اذهب إلى المحتوى

التجميعات (Collections) في dot NET


رضوى العربي

مهيئ التجميعات (Collection Initializers)

يُمكِن تَهيِّئة (Initialize) بعض أنواع التَجمِيعَات أثناء التصريح (declaration) عنها. على سبيل المثال، تُنشِئ التَعلِيمَة البرمجية التالية المُتغَير numbers وتُهيِّئه بمجموعة أعداد (integers):

List<int> numbers = new List<int>(){10, 9, 8, 7, 7, 6, 5, 10, 4, 3, 2, 1};

في الواقع، يُحوِل مُصرِّف ‎(Compiler) c‎#‎ تَعلِيمَة التَهيِّئة السابقة لسلسلة استدعاءات للتابع Add. بناءً على ذلك، لا يُمكِن استخدام هذه الصِيْغة (syntax) إلا مع التَجمِيعَات التي تُدعِم التابع Add.

لاحظ أن صَنفيّ المَكْدَس Stack<T>‎ والرَتَل Queue<T>‎ لا يُدعِمَانِها.

عِند التَعامُل مع التَجمِيعَات المُعقدة مِثل Dictionary<TKey, TValue>‎، المُكَوّنة مِن أزواج مفتاح/قيمة (key/value pairs)، يُحدَّد كل زوج كنوع مَجهول الاسم (anonymous type) بقائمة التَهيِّئة (initializer list)، كالآتي:

Dictionary<int, string> employee = new Dictionary<int, string>()
{{44, "John"}, {45, "Bob"}, {47, "James"}, {48, "Franklin"}};

العنصر الأول بكل زوج هو المفتاح (key) بينما الثاني هو القيمة (value).

القائمة (List)

قائمة من أنواع أولية (Primitive)

List<int> numbers = new List<int>(){10, 9, 8, 7, 7, 6, 5, 10, 4, 3, 2, 1};

قائمة من أنواع مخصصة (Custom Types) وتهيئتها

تُعرِّف الشيفرة التالية الصَنف Model، الذي يحتوي على خاصيتي Name و Selected من النوع nullable boolean.

public class Model
{
    public string Name { get; set; }
    public bool? Selected { get; set; }
}

توجد عدة طرائق لتَهيِّئة قائمة List‎ من الصنف Model. إذا لم يُعرَّف بَانِي الكائن (Constructor) بالصنف كالشيفرة بالأعلى، تُنشَّئ نسخ جديدة من الصنف Model وتُهيَّئ كالتالي:

var SelectedEmployees =
{
    new Model() {Name = "Item1", Selected = true},
    new Model() {Name = "Item2", Selected = false},
    new Model() {Name = "Item3", Selected = false},
    new Model() {Name = "Item4"}
};

أما لو عُرِّف بَانِي الكائن (Constructor) بالصَنف كالتالي:

public class Model
{
    public Model(string name, bool? selected = false)
    {
        Name = name;
        selected = Selected;
    }
    public string Name { get; set; }
    public bool? Selected { get; set; }
}

يَسمَح ذلك بتَهيِّئة القائمة بشكل مختلف قليلًا، كالتالي:

var SelectedEmployees = new List<Model>
{
    new Model("Mark", true),
    new Model("Alexis"),
    new Model("")
};

ماذا عن صنف إِحِدى خواصه هي صنف بذاته؟ مثلًا:

public class Model
{
    public string Name { get; set; }
    public bool? Selected { get; set; }
}


public class ExtendedModel : Model
{
    public ExtendedModel()
    {
        BaseModel = new Model();
    }
    public Model BaseModel { get; set; }
    public DateTime BirthDate { get; set; }
}

لتبسِيط المثال قليلًا، أُزيِل بَانِي الكائن (constructor) من الصنف Model.

var SelectedWithBirthDate = new List<ExtendedModel>
{
    new ExtendedModel()
    {
        BaseModel = new Model { Name = "Mark", Selected = true},
        BirthDate = new DateTime(2015, 11, 23)
    },
    new ExtendedModel()
    {
        BaseModel = new Model { Name = "Random"},
        BirthDate = new DateTime(2015, 11, 23)
    }
}

بالمِثل، يُمكِن تطبيق ذلك على الأصناف Collection<ExtendedModel>‎ أو ExtendedModel[]‎ و object[]‎ أو [].

المكدس (Stack)

تُدعِم ‎.NET تَجمِيعَة المَكْدَس (Stack)، وهي هيكل بياني (data structure) لإدارة مجموعة من القيم اِعتمادًا على مفهوم “الداخل آخرًا، يخرج أولًا“ (LIFO). يُدخِل التابع Push(T item) العناصر للمَكْدَس، بينما يُخرِج التابع Pop() آخر عنصر تَمْ إدخاله للمَكْدَس، ويَحذِفه منه.

تُوضِح الشيفرة التالية استخدام النسخة المُعمَّمة (Generic) من المَكْدَس لإدارة مجموعة سلاسل نصية. قم أولًا بإضافة فَضَاء الاسم (namespace):

using System.Collections.Generic;

ثم استخدمه:

Stack<string> stack = new Stack<string>();
stack.Push("John");
stack.Push("Paul");
stack.Push("George");
stack.Push("Ringo");


string value;
value = stack.Pop(); // return Ringo
value = stack.Pop(); // return George
value = stack.Pop(); // return Paul
value = stack.Pop(); // return John

يُوجد نسخة غير مُعمَّمة من المَكْدَس تَتَعَامَل مع الكائنات (objects). أضِف فَضَاء الاسم:

using System.Collections;

تُوضح الشيفرة التالية استخدام مَكْدَس غير مُعمَّم:

Stack stack = new Stack();
stack.Push("Hello World"); // string
stack.Push(5); // int
stack.Push(1d); // double
stack.Push(true); // bool
stack.Push(new Product()); // Product object


object value;
value = stack.Pop(); // return Product (Product type)
value = stack.Pop(); // return true (bool)
value = stack.Pop(); // return 1d (double)
value = stack.Pop(); // return 5 (int)
value = stack.Pop(); // return Hello World (string)

بخلاف التابع Pop()‎، يُخرِج التابع Peek() آخر عنصر تَمْ إدخاله للمَكْدَس دون حَذفه منه.

Stack<int> stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
var lastValueAdded = stack.Peek(); // 20

يُطبّق التِكرار (iterate) على عناصر المَكْدَس وفقًا لمفهوم “الداخل آخرًا، يخرج أولًا“ (LIFO) وبدون حذفها، كاﻵتي:

Stack<int> stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);
stack.Push(40);
stack.Push(50);


foreach (int element in stack)
{
    Console.WriteLine(element);
}

خَرْج الشيفرة باﻷعلى يكون كالتالي:

50
40
30
20
10

الرتل (Queue)

تُدعِم ‎.NET تَجمِيعَة الرَتَل (Queue)، وهي هيكل بياني (data structure) لإدارة مجموعة من القيم اِعتمادًا على مفهوم “الداخل أولًا، يخرج أولًا“ (FIFO). يُدخِل التابع Enqueue(T item) العناصر للرَتَل، بينما يُخرِج التابع Dequeue() أول عنصر من الرَتَل، ويَحذِفه منه.

توضح الشيفرة التالية استخدام النسخة المُعمَّمة (Generic) من رَتَل لإدارة مجموعة سلاسل النصية. قم أولًا بإضافة فَضَاء الاسم (namespace):

using System.Collections.Generic;

ثم استخدمه:

Queue<string> queue = new Queue<string>();
queue.Enqueue("John");
queue.Enqueue("Paul");
queue.Enqueue("George");
queue.Enqueue("Ringo");


string dequeueValue;
dequeueValue = queue.Dequeue(); // return John
dequeueValue = queue.Dequeue(); // return Paul
dequeueValue = queue.Dequeue(); // return George
dequeueValue = queue.Dequeue(); // return Ringo

يُوجد نسخة غير مُعمَّمة من الرَتَل تَتَعَامَل مع الكائنات (objects). بالمِثل، أضف فضاء الاسم:

using System.Collections;

تُوضح الشيفرة التالية استخدام رَتَل غير مُعمَّم:

Queue queue = new Queue();
queue.Enqueue("Hello World"); // string
queue.Enqueue(5); // int
queue.Enqueue(1d); // double
queue.Enqueue(true); // bool
queue.Enqueue(new Product()); // Product object


object dequeueValue;
dequeueValue = queue.Dequeue(); // return Hello World (string)
dequeueValue = queue.Dequeue(); // return 5 (int)
dequeueValue = queue.Dequeue(); // return 1d (double)
dequeueValue = queue.Dequeue(); // return true (bool)
dequeueValue = queue.Dequeue(); // return Product (Product type)

بخلاف التابع Dequeue()‎، يُخرِج التابع Peek() أول عنصر بالرَتَل دون حَذفه منه.

Queue<int> queue = new Queue<int>();
queue.Enqueue(10);
queue.Enqueue(20);
var lastValueAdded = queue.Peek(); // 10

يُطبّق التِكرار (iterate) على عناصر الرَتَل وفقًا لمفهوم “الداخل أولًا، يخرج أولًا“ (FIFO) وبدون حذفها، كاﻵتي:

Queue<int> queue = new Queue<int>();
queue.Enqueue(10);
queue.Enqueue(20);
queue.Enqueue(30);
queue.Enqueue(40);
queue.Enqueue(50);


foreach (int element in queue)
{
    Console.WriteLine(i);
}

خَرْج الشيفرة باﻷعلى يكون كالتالي:

10
20
30
40
50

القاموس (Dictionary)

تهيئة القاموس

باستخدام مُهَيِّئ التَجمِيعَات (Collection Initializer):

// Translates to `dict.Add(1, "First")` etc.
var dict = new Dictionary<int, string>()
{
    { 1, "First" },
    { 2, "Second" },
    { 3, "Third" }
};


// Translates to `dict[1] = "First"` etc.
// Works in C# 6.0.
var dict = new Dictionary<int, string>()
{
    [1] = "First",
    [2] = "Second",
    [3] = "Third"
};

الإضافة للقاموس

Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1, "First");
dict.Add(2, "Second");


// To safely add items (check to ensure item does not already exist - would throw)
if(!dict.ContainsKey(3)) 
{
    dict.Add(3, "Third");
}

يُمكِن للمُفهرِس (Indexer) الإضافة للقاموس أيضًا كبديل عن التابع Add. يَبدو المُفهرِس، من الداخل، كأيّ خَاصية (Property) تَملُك جَالِب (getter) وضَابِط (setter) خاص بها، بِخلاف كونهما يَستقبِلان مُعامِلات من أي نوع تُحدِّد بين قوْسين مَعقُوفَين [].

لاحظ المثال التالي:

Dictionary<int, string> dict = new Dictionary<int, string>();
dict[1] = "First";
dict[2] = "Second";
dict[3] = "Third";

عِند مُحاولة إضافة عنصر مفتاحه (Key) مَوجُود مُسبَقًا بالقاموس، يَستبدِل المُفهرِس القيمة الجديدة بالقيمة الموجودة، هذا بِخلاف التابع Add الذي يُبلِّغ عن اعتراض (Exception).

لتَنشِئة قاموس آمن خيطيًا، استخدم الصنف ConcurrentDictionary<TKey, TValue>‎، كالآتي:

var dict = new ConcurrentDictionary<int, string>();
dict.AddOrUpdate(1, "First", (oldKey, oldValue) => "First");

جلب قيمة من القاموس

انظر لشيفرة التهيئة التالية:

var dict = new Dictionary<int, string>()
{
    { 1, "First" },
    { 2, "Second" },
    { 3, "Third" }
};

قَبل مُحاولة استخدام مفتاح (key) لجَلْب قيمته (value) المُناظِرة من القاموس، يُمكِنك أولًا استخدام التابع ContainsKey لاختبار وُجُود المفتاح؛ وذلك لِتجَنُب التَبلِّيغ عن اعتراض من النوع KeyNotFoundException في حالة عَدَم وُجُود المفتاح.

if (dict.ContainsKey(1))
    Console.WriteLine(dict[1]);

تُعانِي الشيفرة باﻷعلى، مع ذلك، مِن عَيْب البَحَث بالقاموس مَرتين (مرَّة لاختبار وجود المفتاح ومرَّة للجَلْب الفِعلِيّ للقيمة). إذا كان القاموس (Dictionary) كبيرًا، فسيُؤثِر ذلك على مستوى أداء الشيفرة. لحسن الحظ، يُمكِن للتابع TryGetValue إجراء العَمليتين (البحث والجَلْب) سَويًا، كالتالي:

string value;
if (dict.TryGetValue(1, out value))
    Console.WriteLine(value);

جعل القاموس Dictionary<string, T>‎ لا يَتأثَر بحالة حروف المفاتيح (keys)

var MyDict = new Dictionary<string,T>(StringComparison.InvariantCultureIgnoreCase)

تعديد (Enumerating) القاموس

يُمكِنك تَعديد قاموس بِطَرِيقة مِن ثَلَاث:

  • الطريقة اﻷولى: استخدام أزواج KeyValue:
Dictionary<int, string> dict = new Dictionary<int, string>();
foreach(KeyValuePair<int, string> kvp in dict)
{
    Console.WriteLine("Key : " + kvp.Key.ToString() + ", Value : " + kvp.Value);
}
  • الطريقة الثانية: استخدام الخاصية Keys:
Dictionary<int, string> dict = new Dictionary<int, string>();
foreach(int key in dict.Keys)
{
    Console.WriteLine("Key : " + key.ToString() + ", Value : " + dict[key]);
}
  • الطريقة الثالثة: استخدام الخاصية Values:
Dictionary<int, string> dict = new Dictionary<int, string>();
foreach(string s in dict.Values)
{
    Console.WriteLine("Value : " + s);
}

تحويل الواجهة IEnumerable إلى قاموس

إصدار ‎.NET 3.5 أو أحدث

يُنشئ التابع ToDictionary نُسخَة من النوع Dictionary,> من الواجهة IEnumerable كالتالي:

using System;
using System.Collections.Generic;
using System.Linq;


public class Fruits
{
    public int Id { get; set; }
    public string Name { get; set; }
}


var fruits = new[]
{
    new Fruits { Id = 8 , Name = "Apple" },
    new Fruits { Id = 3 , Name = "Banana" },
    new Fruits { Id = 7 , Name = "Mango" },
};


// Dictionary<int, string>
key
    value
    var dictionary = fruits.ToDictionary(x => x.Id, x => x.Name);

تحويل القاموس إلى قائِمة (List)

تَنشِّئة قائمة من زوج مفتاح/قيمة KeyValuePair:

Dictionary<int, int> dictionary = new Dictionary<int, int>();
List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>();
list.AddRange(dictionary);

تَنشِّئة قائمة من مفاتيح القاموس (keys):

Dictionary<int, int> dictionary = new Dictionary<int, int>();
List<int> list = new List<int>();
list.AddRange(dictionary.Keys);

تَنشِّئة قائمة من قيم القاموس (values):

Dictionary<int, int> dictionary = new Dictionary<int, int>();
List<int> list = new List<int>();
list.AddRange(dictionary.Values);

الحذف من القاموس

انظر لشيفرة التهيئة التالية:

var dict = new Dictionary<int, string>()
{
    { 1, "First" },
    { 2, "Second" },
    { 3, "Third" }
};

يَحذِف التابع Remove مفتاحًا مُعَيَنًا وقِيمته من القاموس كالآتي:

bool wasRemoved = dict.Remove(2);

يُعِيد التابع Remove قِيمَة مِن النوع boolean، تكون true إذا ما وَجَدَ التابع المفتاح المُحدَّد واستطاع حَذفُه من القاموس. أو تكون false إذا لم يَجِده (مما يعّنِي أنه لا يُبلِّغ عن اعتراض).

من غَيْر الصحيح أن تُحِاول حَذْف مفتاح من القاموس بإِسنَاد القيمة الفارغة null إليه، كالآتي:

// WRONG WAY TO REMOVE!
dict[2] = null; 

سَتَستبدِل الشيفرة باﻷعلى القيمة الفارغة null بقيمة المفتاح السابقة فقط دُون الحَذفُ الفِعِلِّي للمفتاح.

استخدم التابع Clear لحذف جميع المفاتيح والقيم من القاموس، كالآتي:

dict.Clear();

بالرغم من أن التابع Clear يُحرِر جميع مَراجِع العناصر الموجودة بالقاموس، ويُعيد ضَبْط قيمة الخاصية Count للصفر، فإنه لا يُحرِر سَعَة المصفوفة الداخلية وتظل كما هي.

فحص وجود مفتاح بالقاموس

يُجري التابع ContainsKey(TKey) اختبار وُجُود مفتاح (key) بقاموس (Dictionary). يُمَرَّر المفتاح كمُعامِل من النوع TKey للتابع الذي يُعيد قيمة منطقية bool تُحدد ما إذا كان المفتاح موجودًا أم لا.

var dictionary = new Dictionary<string, Customer>()
{
    {"F1", new Customer() { FirstName = "Felipe", ...}},
    {"C2", new Customer() { FirstName = "Carl", ... }},
    {"J7", new Customer() { FirstName = "John", ... }},
    {"M5", new Customer() { FirstName = "Mary", ... }},
};

لاختبار وجود المفتاح C2 بالقاموس:

if (dictionary.ContainsKey("C2"))
{
    // exists
}

تُدعِم النُسخة المُعمَّمة (Generic) من القاموس Dictionary,> هذا التابع ContainsKey أيضًا.

القاموس المتزامن ConcurrentDictionary<TKey, TValue>‎

يتوفَّر -منذ إصدار .NET 4.0 أو أحدث- القاموس المُتَزَامِن وهو تَجمِيعة آمِنة خيطيًا (thread-safe) مُكوَّنة مِن أزواج مفتاح/قيمة (key/value pairs)، بحيث يمكن لخُيُوط (threads) مُتعدِّدة أن تَلِج إليها وُلُوجًا مُتَزَامِنًا.

إنشاء نسخة

مِثِل تَنشِئة نسخة من Dictionary<TKey, TValue>‎، كالآتي:

var dict = new ConcurrentDictionary<int, string>();

إِضافة أو تحدِيث

ربما تُفَاجئ بِعَدَم وجود تابع Add، حيث يُجرِي التابع AddOrUpdate كِلَا العَمَليتين. في الواقع، تتوفر بصمتين (Method Overloading) من التابع AddOrUpdate.

اﻷولى:

AddOrUpdate(TKey key, TValue, Func<TKey, TValue, TValue> addValue)‎

إذا لم يَكُن المفتاح موجودًا مُسبَقًا، يُضِيف هذا الشكل من التابع زوج مفتاح/قيمة جديد. أما إذا كان موجودًا، يُحدِّثه مُعامِل الدالة المُمَرَّر للتابع addValue.

الثانية:

AddOrUpdate(TKey key, Func<TKey, TValue> addValue, Func<TKey, TValue, TValue> updateValueFactory)‎

تُحَدِث أو تُضيِف مُعامِلات الدوال، المُمَرَّرة لهذا الشكل من التابع، زوج مفتاح/قيمة (key/value pair) إذا كان المفتاح موجودًا مسبقًا أم لا على الترتيب.

تُضُيِف أو تُحَدِث البصمة الأولى من التابع قيمة مُفتاح مُعين بغض النظر عن القيمة السابقة إن وُجِدّت، كالآتي:

string addedValue = dict.AddOrUpdate(1, "First", (updateKey, valueOld) => "First");

كالمثال باﻷعلى، تُضُيِف البصمة الأول من التابع قيمة مفتاح معين، لكنها في حالة التحديث، تَعتمِد على القيمة السابقة للمفتاح كالآتي:

string addedValue2 = dict.AddOrUpdate(1, "First", (updateKey, valueOld) => $"{valueOld} Updated");

يُمكِن للبصمة الثانية من التابع الإضافة أيضًا من خلال مُعامِل دَالة مُمَرَّر لها كالآتي:

string addedValue3 = dict.AddOrUpdate(1, (key) => key == 1 ? "First" : "Not First", (updateKey, valueOld) => $"{valueOld} Updated");

جلب قيمة (value)

مثل جَلْب قيمة من Dictionary<TKey, TValue>‎، كالآتي:

string value = null;
bool success = dict.TryGetValue(1, out value);

جلب مع إضافة قيمة

تتوفر بصمتين من التابع GetOrAdd لجلب قيمة مفتاح معين أو إضافته للقاموس بطريقة آمِنة خيطيًا.

تَجلْب البصمة الأولى من التابع GetOrAdd قيمة المفتاح 2 أو تضيفها إن لم يكن المفتاح موجودًا بالقاموس كالآتي:

string theValue = dict.GetOrAdd(2, "Second");

كالمثال السابق، تَجلْب البصمة الثانية من التابع قيمة المفتاح 2 إن وُجِدَّت، لكنها تُضِيفها باستخدام مُعامِل الدالة المُمَرَّر لها إن لم تَكُن موجودة، كالآتي:

string theValue2 = dict.GetOrAdd(2, (key) => key == 2 ? "Second" : "Not Second." );

تعزيز القاموس المتزامن باستخدام الإرجاء (Lazy) لخفض الحوسبة المكررة

المُشكِلة:

يَبزُغ دور القاموس المُتَزَامِن ConcurrentDictionary عندما يكون المفتاح المطلوب مُخزنًا بالذاكرة المخبئيِّة (cache) فيُعَاد الكائن المناظر للمفتاح فوريًا عادةً بدون قِفل (lock free). لكن ماذا لو لم يكن المفتاح موجودًا بالذاكرة المخبئيِّة (cache misses) وكانت عملية تنشئة الكائن مُكلِفة بصورة تفوق كُلفة تبديل سياق الخيط (thread-context switch)؟

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

الحل:

لتجاوز تلك المشكلة، يُمكِن الجمع بين القاموس المُتَزَامِن ConcurrentDictionary<TKey, TValue>‎ والتهيئة المُرجأة باستخدام الصنف Lazy<TValue>‎. الفكرة ببساطة تعتمد على كَوْن تابع القاموس المُتَزَامِن GetOrAdd يُعيِد فقط القيم المُضافة فعليًا للتَجمِيعَة. لاحظ أنه، في هذه الحالة أيضًا، قد تُهْدَر الكائنات المُرجأة (Lazy objects)، لكن لا يُمكن عَدَّ ذلك مشكلة كبيرة، وبخاصة أن الكائنات المُرجأة ليست مُكلِفة بالموازنة مع الكائن الفِعلِي، وبالطبع أنت ذكي كفاية لألا تَطلُب خاصية Value الموجودة بتلك الكائنات، وإنما يُفترض بك أن تطلبها فقط للكائنات العائدة من التابع GetOrAdd مما يَضمن كوْنها مُضافة فِعليًا للتَجميعة.

public static class ConcurrentDictionaryExtensions
{
    public static TValue GetOrCreateLazy<TKey, TValue>(
        this ConcurrentDictionary<TKey, Lazy<TValue>> d,
        TKey key,
        Func<TKey, TValue> factory)
    {
        return
            d.GetOrAdd(
            key,
            key1 =>
            new Lazy<TValue>(() => factory(key1),
                             LazyThreadSafetyMode.ExecutionAndPublication)).Value;
    }
}

قد يكْون التخزين المؤقت (caching) للكائنات من الصنف XmlSerializer بالتحديد مُكلِفًا، مع وجود كثير من التنازع (contention) أثناء بدء تشغيل (startup) البرنامج. علاوة على ذلك، إذا كانت المُسَلسِلَات مُخصّصة (custom serializers)، ستُسَرَّب الذاكرة (memory leak) أثناء بقية دورة حياة العمليّة (process lifecycle).

الفائدة الوحيدة من استخدام القاموس المُتزامِن ConcurrentDictionary في هذه الحالة هو تجنب الأقفال (locks) أثناء بقية دورة حياة العمليّة (process lifecycle)، ولكن لن يكون وقت بدء تشغيل البرنامج واستهلاك الذاكرة مقبولًا. وهنا يَبزُغ دور القاموس المتزامن المُعَّزز بالإِرجَاء المُذكور سلفًا.

private ConcurrentDictionary<Type, Lazy<XmlSerializer>> _serializers =
    new ConcurrentDictionary<Type, Lazy<XmlSerializer>>();


public XmlSerializer GetSerialier(Type t)
{
    return _serializers.GetOrCreateLazy(t, BuildSerializer);
}


private XmlSerializer BuildSerializer(Type t)
{
    throw new NotImplementedException("and this is a homework");
}

تجمِيعات القراءة فقط ReadOnlyCollections

تنشئة تجميعة قراءة فقط

  • باستخدام بَانِي الكائن (Constructor)

تُنشَّئ تجميعة قراءة فقط ReadOnlyCollection بتَمريِر كائن واجهة IList إلى بَانِي الكائن الخاص بالصنف ReadOnlyCollection، كالتالي:

var groceryList = new List<string> { "Apple", "Banana" };
var readOnlyGroceryList = new ReadOnlyCollection<string>(groceryList);
  • باستخدام استعلامات LINQ

تُوفِر استعلامات LINQ تابع مُوسِّع AsReadOnly()‎ لكائنات وَاجِهة IList، كالتالي:

var readOnlyVersion = groceryList.AsReadOnly();

عادة ما تُريد الاحتفاظ بمَرجِع مَصْدَر التَجمِيعَة بِهَدف التعديل عليه خاصًا (private)، بينما تَسمَح بالولُوج العَلنِي (public) إلى تَجمِيعَة القراءة فقط ReadOnlyCollection. في حِين تستطيع تَنشِّئة نسخة القراءة فقط مِن قائمة ضِمنيّة (in-line list) كالمثال التالي، لن تكون قادرًا على تعديل التَجمِيعَة بعد إِنشَّائها.

var readOnlyGroceryList = new List<string> {"Apple", "Banana"}.AsReadOnly();

عظيم! لكنك لن تكون قادرًا على تحديث القائمة لأنك لم تعد تمتلك مرجع للقائمة اﻷصلية.

إذا كانت التَنشِّئة من قائمة ضِمنيّة مناسبة لغَرضَك، رُبما يُفترض بك استخدام هيكل بيانات آخر مثل التَجمِيعَة الثابتة ImmutableCollection.

تحديث تجميعة قراءة فقط

كما ذكرنا مُسبَقًا، لا يُمكنك تعديل تَجمِيعَة القراءة فقط ReadOnlyCollection مباشرة. بدلًا من ذلك، يُحَدَّث مَصْدَر التَجمِيعَة الذي بِدورِه يؤدي إلى تحديث تَجمِيعَة القراءة فقط. يُعد ذلك مِيزة رئيسية لتَجمِيعَات القراءة فقط ReadOnlyCollection.

var groceryList = new List<string> { "Apple", "Banana" };


var readOnlyGroceryList = new ReadOnlyCollection<string>(groceryList);


var itemCount = readOnlyGroceryList.Count;         // تحتوي على عنصرين


//readOnlyGroceryList.Add("Candy");                 // خطأ تصريفي، لا يُمكن إضافة عناصر لكائن من الصنف ReadOnlyCollection 


groceryList.Add("Vitamins");                                 // لكن يمكن إضافتهم إلى التجميعة الأصلية


itemCount = readOnlyGroceryList.Count;                 // الآن، تحتوي على ثلاثة عناصر
var lastItem = readOnlyGroceryList.Last();                 // أصبح العنصر اﻷخير بتجميعة القراءة فقط هو "Vitamins"

مِثال حيّ

عناصر تجمِيعات القراءة فقط ليست بالضرورة للقراءة فقط

إذا كانت عناصر مَصْدَر التَجمِيعَة من نوع غَيْر ثابِت (mutable)، فعِندها يُمكن الولُوج إليها من خلال تَجمِيعَة القراءة فقط وتعديلها.

public class Item
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
public static void FillOrder()
{
    // An order is generated
    var order = new List<Item>
    {
        new Item { Name = "Apple", Price = 0.50m },
        new Item { Name = "Banana", Price = 0.75m },
        new Item { Name = "Vitamins", Price = 5.50m }
    };


    // The current sub total is $6.75
    var subTotal = order.Sum(item => item.Price);


    // Let the customer preview their order
    var customerPreview = new ReadOnlyCollection<Item>(order);


    // لا يمكن إضافة أو حذف عناصر من تجميعة القراءة فقط لكن يمكن التعديل على قيمة متغير السعر
    customerPreview.Last().Price = 0.25m;


    // The sub total is now only $1.50!
    subTotal = order.Sum(item => item.Price);
}

مِثال حيّ

ترجمة -وبتصرف- للفصول:

  • Dictionaries
  • Collections
  • ReadOnlyCollections

من كتاب ‎.NET Framework Notes for Professionals


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...