درس دوازدهم – ساختارها در C# (Struct)
در این درس با ساختارها (Struct) در زبان C# آشنا میشویم. اهداف این درس بشرح زیر میباشند
ساختار (struct) چیست؟
همانطور که با استفاده از کلاسها میتوان انواع (types) جدید و مورد نظر را ایجاد نمود، با استفاده از struct ها میتوان انواع مقداری (value types) جدید و مورد نظر را ایجاد نمود. از آنجائیکه struct ها بعنوان انواع مقداری در نظر گرفته میشوند، از اینرو تمامی اعمال مورد استفاده بر روی انواع مقداری را میتوان برای struct ها در نظر گرفت. struct ها بسیار شبیه به کلاسها هستند و میتوانند دارای فیلد، متد و property باشند. عموماً ساختارها مجموعه کوچکی از عناصری هستند که منطقی با یکدیگر دارای رابطه میباشند. برای نمونه میتوان به ساختار Point موجود در Framework SDK اشاره کرد که حاوی دو property با نامهای X و Y است.
با استفاده از ساختارها (struct) میتوان اشیایی با انواع جدید ایجاد کرد که این اشیاء میتوانند شبیه به انواع موجود (int, float, …) باشند. حال سوال اینست که چه زمانی از ساختارها(struct) بجای کلاس استفاده میکنیم؟ در ابتدا به نحوه استفاده از انواع موجود در زبان C# توجه نمایید. این انواع دارای مقادیر و عملگرهای معینی جهت کار با این مقادیر هستند. حال اگر نیاز به شیای دارید که همانند این انواع رفتار نمایند لازم است تا از ساختارها (struct) استفاده نمایید. در ادامه این مبحث نکات و قوانینی را ذکر میکنیم که با استفاده از آنها بهتر بتوانید از ساختارها (struct) استفاده نمایید.
اعلان و پیادهسازی struct
برای اعلان یک struct کافیست تا با استفاده از کلمه کلیدی struct که بدنبال آن نام مورد نظر برای ساختار آمده استفاده کرد. بدنة ساختار نیز بین دو کروشة باز و بسته {} قرار خواهد گرفت. به مثال زیر توجه نمایید :
مثال 1-12 : نمونهای از یک ساختار (Struct)
using System;
struct Point
{
public int x;
public int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public Point Add(Point pt)
{
Point newPt;
newPt.x = x + pt.x;
newPt.y = y + pt.y;
return newPt;
}
}
///
/// struct مثالی از اعلان و ساخت یک
///
class StructExample
{
static void Main(string[] args)
{
Point pt1 = new Point(1, 1);
Point pt2 = new Point(2, 2);
Point pt3;
pt3 = pt1.Add(pt2);
Console.WriteLine("pt3: {0}:{1}", pt3.x, pt3.y);
}
}
مثال 1-12 نحوة ایجاد و استفاده از struct را نشان میدهد. به راحتی میتوان گفت که یک نوع(type) ، یک struct است، زیرا از کلمه کلیدی struct در اعلان خود بهره میگیرد. ساختار پایهای یک ساختار پایهای یک struct بسیار شبیه به یک کلاس است، ولی تفاوتهایی با آن دارد که این تفاوتها در پاراگراف بعدی مورد بررسی قرار میگیرند. ساختار Point دارای سازنده ایست که مقادیر داده شده با آنرا به فیلدهای x و y تخصیص میدهد. این ساختار همچنین دارای متد Add() میباشد که ساختار Point دیگری را دریافت میکند و آنرا به struct کنونی میافزاید و سپس struct جدیدی را باز میگرداند.
توجه نمایید که ساختار Point جدیدی درون متد Add() تعریف شده است. توجه کنید که در اینجا همانند کلاس، نیازی به استفاده از کلمه کلیدی new جهن ایجاد یک شیء جدید نمیباشد. پس از آنکه نمونة جدیدی از یک ساختار ایجاد شد، سازندة پیش فرض (یا همان سازندة بدون پارامترش) برای آن در نظر گرفته میشود. سازندة بدون پارامتر کلیه مقادیر فیلدهای ساختار را به مقادیر پیش فرض تغییر میدهد. بعنوان مثال فیلدهای صحیح به صفر و فیلدهای Boolean به false تغییر میکنند. تعریف سازندة بدون پارامتر برای یک ساختار صحیح نمیباشد. (یعنی شما نمیتوانید سازندة بدون پارامتر برای یک struct تعریف کنید.)
ساختارها (structs) با استفاده از عملگر new نیز قابل نمونهگیری هستند (هر چند نیازی به استفاده از این عملگر نیست.) در مثال 1-12 pt1 و pt2 که ساختارهایی از نوع Point هستند، با استفاده از سازندة موجود درون ساختار Point مقداردهی میشوند. سومین ساختار از نوع Point، pt3 است و از سازندة بدون پارامتر استفاده میکند زیرا در اینجا مقدار آن اهمیتی ندارد. سپس متد Add() از ساختار pt1 فراخوانده میشود و ساختار pt2 را بعنوان پارامتر دریافت میکند. نتیجه به pt3 تخصیص داده میشود، این امر نشان میدهد که یک ساختار میتواند همانند سایر انواع مقداری مورد استفاده قرار گیرد. خروجی مثال 1-12 در زیر نشان داده شده است :
pt3 : 3 : 3
یکی دیگر از تفاوتهای ساختار و کلاس در اینست که ساختارها نمیتوانند دارای تخریب کننده (deconstructor) باشند. همچنین ارثبری در مورد ساختارها معنی ندارد. البته امکان ارثبری بین ساختارها و interface ها وجود دارد. یک interface نوع مرجعی زبان C# است که دارای اعضایی بدون پیادهسازی است. هر کلاس و یا ساختاری که از یک interface ارثبری نماید باید تمامی متدهای آنرا پیادهسازی کند. دربارة interface ها در آینده صحبت خواهیم کرد.
خلاصه :
هم اکنون شما با چگونگی ایجاد یک ساختار آشنا شدید. هنگامیکه قصد دارید نوعی را بصورت ساختار یا کلاس پیادهسازی کنید، باید به این نکته توجه کنید که این نوع چگونه مورد استفاده قرار میگیرد. اگر میخواهید سازندهای بدون پارامتر داشته باشید، در اینصورت کلاس تنها گزینه شماست. همچنین توجه نمایید از آنجائیکه یک ساختار بعنوان یک نوع مقداری در نظر گرفته میشود، در پشته (Stack) ذخیره میشود و حال آنکه کلاس در heap ذخیره میگردد.
نکات مهم و مطالب کمکی
همانطور که بطور مختصر در بالا نیز اشاره شد، از نظر نوشتاری (syntax) struct و کلاس بسیار شبیه به یکدیگر هستند اما دارای تفاوتهای بسیار مهمی با یکدیگر میباشند.
همانطور که قبلاً نیز اشاره شد شما نمیتوانید برای یک struct سازندهای تعریف کنید که بدون پارامتر است، یعنی برای ایجاد سازنده برای یک struct حتماً باید این سازنده دارای پارامتر باشد. به قطعه کد زیر توجه کنید :
struct Time
{
public Time() { ... } // خطای زمان کامپایل رخ میدهد
⋮
}
پس از اجرای کد فوق کامپایلر خطایی را ایجاد خواهد کرد بدین عنوان که سازندة struct حتماٌ باید دارای پارامتر باشد. حال اگر بجای struct از کلمه کلیدی calss استفاده کرده بودیم این کد خطایی را ایجاد نمیکرد. در حقیقت تفاوت در اینست که در مورد struct، کامپایلر اجازة ایجاد سازندة پیش فرض جدیدی را به شما نمیدهد ولی در مورد کلاس چنین نیست. هنگام اعلان کلاس در صورتیکه شما سازندة پیش فرضی اعلان نکرده باشید، کامپایلر سازندهای پیش فرض برای آن در نظر میگیرد ولی در مورد struct تنها سازندة پیش فرضی معتبر است که کامپایلر آنرا ایجاد نماید نه شما !
یکی دیگر از تفاوتهای بین کلاس و struct در آن است که، اگر در کلاس برخی از فیلدهای موجود در سازندة کلاس را مقداردهی نکنید، کامپایلر مقدار پیش فرض صفر، false و یا null را برای آن فیلد در نظر خواهد گرفت ولی در struct تمامی فیلدهای سازنده باید بطور صریح مقداردهی شوند و درصورتیکه شما فیلدی را مقداردهی نکید کامپایلر هیچ مقداری را برای آن در نظر نخواهدگرفت و خطای زمان کامپایل رخ خواهد داد. بعنوان مثال در کد زیر اگر Time بصورت کلاس تعریف شده بود خطایی رخ نمیداد ولی چون بصورت struct تعریف شده خطای زمان کامپایل رخ خواهد داد :
struct Time
{
public Time(int hh, int mm)
{
hours = hh;
minutes = mm;
} // خطای زمان کامپایلی بدین صورت رخ میدهد : seconds not initialized
⋮
private int hours, minutes, seconds;
}
تفاوت دیگر کلاس و struct در اینست که در کلاس میتوانید در هنگام اعلان فیلدها را مقداردهی کنید حال آنکه در struct چنین عملی باعث ایجاد خطای زمان کامپایل خواهد شد. همانند کدهای فوق، در کد زیر اگر از کلاس بجای struct استفاده شده بود خطا رخ نمیداد :
struct Time
{
⋮
private int hours = 0; // خطای زمان کامپایل رخ میدهد
private int minutes;
private int seconds;
}
آخرین تفاوت بین کلاس و struct که ما به آن خواهیم پرداخت در مورد ارثبری است. کلاسها میتوانند از کلاس پایة خود ارثبری داشته باشند در حالیکه ارثبری در struct ها معنایی ندارد و یک struct تنها میتواند از واسطها (interface) ارثبری داشته باشد.
همانطور که گفتیم، ساختارها روشی برای ایجاد انواع جدید مقدار (Value Types) هستند. از اینرو پس از ایجاد یک ساختار میتوان از آن همانند سایر انواع مقداری استفاده نمود. برای استفاده از یک ساختار ایجاد شده کافیست تا نام آنرا قبل از متغیر مورد نظر قرار دهیم تا متغیر مورد نظر از نوع آن ساختار خاص تعریف شود.
struct Time
{
⋮
private int hours, minutes, seconds;
}
class Example
{
public void Method(Time parameter)
{
Time localVariable;
⋮
}
private Time field;
}
آخرین نکتهای که در مورد ساختارها برای چندمین بار اشاره میکنم انست که، ساختارها انواع مقداری هستند و مستقیماً مقدار را در خود نگه میدارند و از اینرو در stack نگهداری میشوند. استفاده از ساختارها همانند سایر انواع مقداری است.
درس یازدهم – اندیکسرها در C# (Indexers)
در این درس با اندیکسرها در C# آشنا میشویم. اهداف این درس به شرح زیر میباشند :
اندیکسرها
اندیکسرها مفهومی بسیار ساده در زبان C# هستند. با استفاده از آنها میتوانید از کلاس خود همانند یک آرایه استفاده کنید. در داخل کلاس مجموعهای از مقادیر را به هر طریقی که مورد نظرتان هست مدیریت کنید. این اشیاؤ میتوانند شامل مجموعهای از اعضای کلاس، یک آرایه دیگر، و یا مجموعهای از ساختارهای پیچیده دادهای باشند، جدا از پیادهسازی داخلی کلاس، دادههای این ساختارها از طریق استفاده از اندیکسرها قابل دسترسی هستند. به مثالی در این زمینه توجه کنید :
مثال 11-1 : نمونهای از یک اندیکسر
using System;
///
/// مثالی ساده از یک اندیکسر
///
class IntIndexer
{
private string[] myData;
public IntIndexer(int size)
{
myData = new string[size];
for (int i=0; i < size; i++)
{
myData[i] = "empty";
}
}
public string this[int pos]
{
get
{
return myData[pos];
}
set
{
myData[pos] = value;
}
}
static void Main(string[] args)
{
int size = 10;
IntIndexer myInd = new IntIndexer(size);
myInd[9] = "Some Value";
myInd[3] = "Another Value";
myInd[5] = "Any Value";
Console.WriteLine(" Indexer Output ");
for (int i=0; i < size; i++)
{
Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);
}
}
}
مثال 11-1 نحوه پیادهسازی اندیکسر را نشان میدهد. کلاس IntIndexer دارای آرایة رشتهای بنام myData میباشد. این آرایه، عنصری خصوصی (private) است و کاربران خارجی (external users) نمیتوانند به آن دسترسی داشته باشند. این آرایه درون سازندة (constructor) کلاس تخصیصدهی میگردد که در آن پارامتر size از نوع int دریافت میشود، از آرایه myData نمونهای جدید ایجاد میگردد، سپس هر یک از المانهای آن با کلمه "empty" مقداردهی میگردد.
عضو بعدی کلاس، اندیکسر است که بوسیلة کلمه کلیدی this و دو براکت تعریف شده است، this[int pos]. این اندیکسر پارامتر موقعیتی pos را دریافت مینماید. همانطور که حتماً تا کنون دریافتهاید پیادهسازی اندیکسر بسیار شبیه به پیادهسازی یک ویژگی (property) است. اندیکسر نیز دارای accessor های set و get است که دقیقاً همانند property عمل میکنند. همانطور که در اعلان این اندیکسر نیز مشاهده میشود، متغیری از نوع رشتهای را باز میگرداند.
در متد Main() شیء جدیدی از IntIndexer ایجاد شده است و مقادیری به آن افزوده میشود و سپس نتایج چاپ میگردند. خروجی این برنامه به شکل زیر است :
Indexer Output
myInd[0]: empty
myInd[1]: empty
myInd[2]: empty
myInd[3]: Another Value
myInd[4]: empty
myInd[5]: Any Value
myInd[6]: empty
myInd[7]: empty
myInd[8]: empty
myInd[9]: Some Value
استفاده از integer جهت دسترسی به آرایهها در اغلب زبانهای برنامهسازی رایج است ولی زبان C# چیزی فراتر از آنرا نیز پشتیبانی میکند. در C# اندیکسرها را میتوان با چندین پارامتر تعریف کرد و هر پارامتر میتواند از نوع خاصی باشد. پارامترهای مختلف بوسیلة کاما از یکدیگر جدا میشوند. پارامترهای مجاز برای اندیکسر عبارتند از : integer، enum و string. علاوه بر آن، اندیکسرها قابل سرریزی (Overload) هستند. در مثال 2-11 تغییراتی در مثال قبل ایجاد کردهایم تا برنامه قابلیت دریافت اندیکسرهای سرریز شده را نیز داشته باشد.
سرریزی اندیکسرها
مثال 2-11 : اندیکسرهای سرریز شده (Overloaded Indexers)
using System;
///
/// پیادهسازی اندیکسرهای سرریز شده
///
class OvrIndexer
{
private string[] myData;
private int arrSize;
public OvrIndexer(int size)
{
arrSize = size;
myData = new string[size];
for (int i=0; i < size; i++)
{
myData[i] = "empty";
}//end of for
}//end of constructor
public string this[int pos]
{
get
{
return myData[pos];
}
set
{
myData[pos] = value;
}
}//end of indexer
public string this[string data]
{
get
{
int count = 0;
for (int i=0; i < arrSize; i++)
{
if (myData[i] == data)
{
count++;
}//end of if
}//end of for
return count.ToString();
}//end of get
set
{
for (int i=0; i < arrSize; i++)
{
if (myData[i] == data)
{
myData[i] = value;
}//end of if
}//end of for
}//end of set
}//end of overloaded indexer
static void Main(string[] args)
{
int size = 10;
OvrIndexer myInd = new OvrIndexer(size);
myInd[9] = "Some Value";
myInd[3] = "Another Value";
myInd[5] = "Any Value";
myInd["empty"] = "no value";
Console.WriteLine(" Indexer Output ");
for (int i=0; i < size; i++)
{
Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);
}//end of for
Console.WriteLine(" Number of "no value" entries: {0}", myInd["no value"]);
}//end of Main()
}//end of class
مثال 2-11 نحوه سرریز کردن اندیکسر را نشان میدهد. اولین اندیکسر که دارای پارامتری از نوع int تحت عنوان pos است دقیقاً مشابه مثال 1-11 است ولی در اینجا اندیکسر جدیدی نیز وجود دارد که پارامتری از نوع string دریافت میکند. get accessor اندیکسر جدید رشتهای را برمیگرداند که نمایشی از تعداد آیتمهایی است که با پارامتر مقداری data مطابقت میکند. set accessor مقدار هر یک از مقادیر ورودی آرایه را که مقدارش با پارامتر data مطابقت نماید را به مقداری که به اندیکسر تخصیص داده میشود، تغییر میدهد.
رفتار (behavior) اندیکسر سرریز شده که پارامتری از نوع string دریافت میکند، در متد Main() نشان داده شده است. در اینجا set accessor مقدار "No value" را به تمام اعضای کلاس myInd که مقدارشان برابر با "empty" بوده است، تخصیص میدهد. این accessor از دستور زیر استفاده نموده است : myInd["empty"] = "No value" . پس از اینکه تمامی اعضای کلاس myInd چاپ شدند، تعداد اعضایی که حاوی "No value" بودهاند نیز نمایش داده میشوند. این امر با استفاده از دستور زیر در get accessor روی میدهد : myInd["No value"]. خروجی برنامه بشکل زیر است :
Indexer Output
myInd[0]: no value
myInd[1]: no value
myInd[2]: no value
myInd[3]: Another Value
myInd[4]: no value
myInd[5]: Any Value
myInd[6]: no value
myInd[7]: no value
myInd[8]: no value
myInd[9]: Some Value
Number of "no value" entries: 7
علت همزیستی هر دو اندیکسر در مثال 2-11 در یک کلاس مشابه، تفاوت اثرگذاری و فعالیت آنهاست. اثرگذاری و تفاوت اندیکسرها از تعداد و نوع پارامترهای موجود در لیست پارامترهای اندیکسر مشخص میگردد. در هنگام استفاده از اندیکسرها نیز، کلاس با استفاده از تعداد و نوع پارامترهای اندیکسرها، میتواند تشخیص دهد که در یک فراخوانی از کدام اندیکسر باید استفاده نماید. نمونهای از پیادهسازی اندیکسری با چند نوع پارامتر در زیر آورده شده است :
public object this[int param1, ..., int paramN]
{
get
{
// process and return some class data
}
set
{
// process and assign some class data
}
}
خلاصه :
هم اکنون شما با اندیکسرها و نحوة پیادهسازی آنها آشنا شدهاید. با استفاده از اندیکسرها میتوان به عناصر یک کلاس همانند یک آرایه دسترسی پیدا کرد. در این مبحث اندیکسرهای سرریز شده و چند پارامتری نیز مورد بررسی قرار گرفتند.
در آینده و در مباحث پیشرفتهتر با موارد بیشتری از استفادة اندیکسرها آشنا خواهید شد.
نکات :
هنگامیکه از دو یا چند اندیکسر درون یک کلاس استفاده میکنیم، سرریزی (Overloading) اندیکسرها رخ میدهد. در هنگام فراخوانی اندیکسرها، کلاس تنها از روی نوع بازگشتی اندیکسر و تعداد پارامترهای آن متوجه میشود که منظور فراخواننده استفاده از کدام اندیسکر بوده است.
همانطور که در این درس مشاهده کردید دسترسی به عناصر اندیکسر همانند آرایهها با استفاده از یک اندیس صورت میپذیرد. با استفاده از این اندیس میتوان به عنصر مورد نظر کلاس دسترسی پیدا نمود.
یک نمونة بسیار جالب از استفادة اندیکسرها کنترل ListBox است. (ListBox عنصری است کنترلی که با استفاده از آن لیستی از عناصر رشتهای نمایش داده میشوند و کاربر با انتخاب یکی از این گزینهها با برنامه ارتباط برقرار میکند. در حقیقت این عنصر کنترلی یکی از روشهای دریافت اطلاعات از کاربر است با این تفاوت که در این روش ورودیهایی که کاربر میتواند وارد نماید محدود شده هستند و از قبل تعیین شدهاند. نمونهای از یک ListBox قسمت انتخاب نوع فونت در برنامة Word است که در آن لیستی از فونتهای موجود در سیستم نمایش داده میشود و کاربر با انتخاب یکی از آنها به برنامه اعلام میکند که قصد استفاده از کدام فونت سیستم را دارد.) ListBox نمایشی از ساختمان داده ایست شبیه به آرایه که اعضای آن همگی از نوع string هستند. علاوه بر این این کنترل میخواهد تا در هنگام انتخاب یکی از گزینههایش بتواند اطلاعات خود را بطور خودکار update نماید و یا به عبارتی بتواند ورودی دریافت نماید. تمامی این اهداف با استفاده از اندیکسر میسر میشود. اندیکسرها شبیه به property ها اعلان میشوند با این تفاوت مهم که اندیکسرها بدون نام هستند و نام آنها تنها کلمه کلیدی this است و همین this مورد اندیکس شدن قرار میگیرد و سایر موارد بشکل پارامتر به اندیکسر داده میشوند.
public class ListBox: Control
{
private string[] items;
public string this[int index]
{
get
{
return items[index];
}
set
{
items[index] = value;
Repaint();
}
}
}
با نگاه به نحوه استفاده از اندیکسر بهتر میتوان با مفهوم آن آشنا شد. برای مثال دسترسی به ListBox بشکل زیر است :
ListBox listBox = ...;
listBox[0] = "hello";
Console.WriteLine(listBox[0]);
نمونه برنامهای که در آن نحوة استفاده از اندیکسر در عنصر کنترلی ListBox نشان داده شده، در زیر آورده شده است :
Csharp-Persian_Indexer_Demo
using System;
public class ListBoxTest
{
// تخصیص داده میشوند.ListBoxرشتههای مورد نظر به
public ListBoxTest(params string[] initialStrings)
{
// فضایی را برای ذخیرهسازی رشتههای تخصیص میدهد.
strings = new String[256];
// رشتههای وارد شده به سازنده را درون آرایهای کپی میکند.
foreach (string s in initialStrings)
{
strings[ctr++] = s;
}
}//end of constructor
// رشتهای به انتهای کنترل افزوده میشود.
public void Add(string theString)
{
if (ctr >= strings.Length)
{
// در این قسمت میتوان کدی جهت کنترل پر شدن فضای تخصیص داده شده قرار داد.
}
else
strings[ctr++] = theString;
}//end of Add()
// اعلان اندیکسر
public string this[int index]
{
get
{
if (index < 0 || index >= strings.Length)
{
// در این قسمت میتوان کدی جهت کنترل پر شدن فضای تخصیص داده شده قرار داد.
}
return strings[index];
}//end of get
set
{
if (index >= ctr )
{
// فراخوانی متدی جهت کنترل خطا
}
else
strings[index] = value;
}//end of set
}//end of indexer
// تعداد رشتههای موجود را نشان میدهد
public int GetNumEntries( )
{
return ctr;
}
private string[] strings;
private int ctr = 0;
}//end of ListBoxTest class
public class Tester
{
static void Main( )
{
//جدید و تخصیص دهی آن ListBox ساخت یک
ListBoxTest lbt = new ListBoxTest("Hello", "World");
// رشتههای مورد نظر به کنترل افزوده میشوند.
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");
// رشتة جدیدی در خانه شمارة یک فرار داده میشود.
string subst = "Universe";
lbt[1] = subst;
// کلیه آیتمهای موجود نمایش داده میشوند.
for (int i = 0;i
{
Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]);
}
}//end of Main()
}//end of Tester class
خروجی نیز بشکل زیر میباشد :
Output:
lbt[0]: Hello
lbt[1]: Universe
lbt[2]: Who
lbt[3]: Is
lbt[4]: John
lbt[5]: Galt
توجه :
مطالب انتهایی این درس کمی پیشرفتهتر و پیچیدهتر از مطالب قبل به نظر میآیند. این انتظار وجود ندارد که شما کلیه مطالب این قسمت را بطور کامل متوجه شده باشید، بلکه هدف تنها آشنا شدن شما با مسایل پیچیدهتر و واقعیتر است. در آیندهای نه چندان دور، در سایت به صورت حرفهای کلیه مطالب و سرفصل های گفته شده را مورد بررسی قرار خواهیم داد. در ابتدا هدف من آشنایی شما با کلیه مفاهیم پایهای زبان C# است تا بعد از این آشنایی به طور کامل و بسیار پیشرفته به بررسی کلیه مفاهیم زبان بپردازیم. پس از اتمام آموزش اولیه تحولات اساسی در سایت مشاهده خواهید کرد و در آن هنگام به بررسی کامل هر مبحث با مثالهایی بسیار واقعی و کاربردی خواهیم پرداخت.
درس دهم – ویژگیها در C#
در این درس با ویژگیها (Properties) در زبان C# آشنا خواهیم شد. اهداف این درس به شرح زیر میباشد :
Property ها امکان ایجاد حفاظت از فیلدهای یک کلاس را از طریق خواندن و نوشتن بوسیله Property را فراهم مینماید. Property ها علاوه بر اینکه از فیلدهای یک کلاس حفاظت میکنند، همانند یک فیلد قابل دسترسی هستند. بمنظور درک ارزش Property ها بهتر است ابتدا به روش کلاسیک کپسوله کردن متدها توجه نمایید.
مثال 1-10 : یک نمونه از چگونگی دسترسی به فیلدهای کلاس به طریقه کلاسیک
using System;
public class PropertyHolder
{
private int someProperty = 0;
public int getSomeProperty()
{
return someProperty;
}
public void setSomeProperty(int propValue)
{
someProperty = propValue;
}
}
public class PropertyTester
{
public static int Main(string[] args)
{
PropertyHolder propHold = new PropertyHolder();
propHold.setSomeProperty(5);
Console.WriteLine("Property Value: {0}", propHold.getSomeProperty());
return 0;
}
}
مثال 1-10 روش کلاسیک دسترسی به فیلدهای یک کلاس را نشان میدهد. کلاس PropertyHolder دارای فیلدی است تمایل داریم به آن دسترسی داشته باشیم. این کلاس دارای دو متد getSomeProperty() و setSomePropery() میباشد. متد getSomeProperty() مقدار فیلد someProperty را باز میگرداند و متد setSomeProperty() مقداری را به فیلد someProperty تخصیص میدهد.
کلاس PropertyTester از متدهای کلاس PropertyHolder جهت دریافت مقدار فیلد someProperty از کلاس PropertyHolder استفاده میکند. در متد Main() نمونه جدیدی از شی PropertyHolder با نام propHold ایجاد میگردد. سپس بوسیله متد setSomeProperty، مقدار someMethod از propHold برابر با 5 میگردد و سپس برنامه مقدار property را با استفاده از فراخوانی متد Console.WriteLine() در خروجی نمایش میدهد. آرگومان مورد استفاده برای بدست آوردن مقدار property فراخوانی به متد getSomeProperty() است که توسط آن عبارت “Property Value : 5” در خروجی نمایش داده میشود.
چنین متد دسترسی به اطلاعات فیلد بسیار خوب است چرا که از نظریه کپسوله کردن شیءگرایی پشتیبانی میکند. اگر پیادهسازی someProperty نیز تغییر یابد و مثلا از حالت int به byte تغییر یابد، باز هم این متد کار خواهد کرد. حال همین مسئله با استفاده از خواص Property ها بسیار سادهتر پیادهسازی میگردد. به مثال زیر توجه نمایید.
مثال 2-10 : دسترسی به فیلدهای کلاس به استفاده از Property ها
using System;
public class PropertyHolder
{
private int someProperty = 0;
public int SomeProperty
{
get
{
return someProperty;
}
set
{
someProperty = value;
}
}
}
public class PropertyTester
{
public static int Main(string[] args)
{
PropertyHolder propHold = new PropertyHolder();
propHold.SomeProperty = 5;
Console.WriteLine("Property Value: {0}", propHold.SomeProperty);
return 0;
}
}
مثال 2-10 چگونگی ایجاد و استفاده از ویژگیها (Property) را نشان میدهد. کلاس PropertyHolder دارای پیادهسازی از ویژگی SomeProperty است. توجه نمایید که اولید حرف از نام ویژگی با حرف بزرگ نوشته شده و این تنها تفاوت میان اسم ویژگی SomeProperty و فیلد someProperty میباشد. ویژگی دارای دو accessor با نامهای set و get است. accessor get مقدار فیلد someProperty را باز میگرداند. set accessor نیز با استفاده از مقدار value، مقداری را به someProperty تخصیص میدهد. کلمه value که در set accessor آورده شده است جزو کلمات رزرو شده زبان C# میباشد.
کلاس PropertyTester از ویژگی someProperty مربوط به کلاس PropertyHolder استفاده میکند. اولین خط در متد Main() شیای از نوع PropertyHolder با نام propHold ایجاد مینماید. سپس مقدار فیلد someProperty مربوط به شیء propHold، با استفاده از ویژگی SomeProperty به 5 تغییر مییابد و ملاحظه مینمایید که مسئله به همین سادگی است و تنها کافی است تا مقدار مورد نظر را به ویژگی تخصیص دهیم.
پس از آن، متد Console.WriteLine() مقدار فیلد someProperty شیء propHold را چاپ مینماید. این عمل با استفاده از ویژگی SomeProperty شیء propHold صورت میگیرد.
ویژگیها را میتوان طوری ایجاد نمود که فقط خواندنی (Read-Only) باشند. برای این منظور تنها کافیست تا در ویژگی فقط از get accessor استفاده نماییم. به مثال زیر توجه نمایید.
ویژگیهای فقط خواندنی (Read-Only Properties)
مثال 3-10 : ویژگیهای فقط خواندنی
using System;
public class PropertyHolder
{
private int someProperty = 0;
public PropertyHolder(int propVal)
{
someProperty = propVal;
}
public int SomeProperty
{
get
{
return someProperty;
}
}
}
public class PropertyTester
{
public static int Main(string[] args)
{
PropertyHolder propHold = new PropertyHolder(5);
Console.WriteLine("Property Value: {0}", propHold.SomeProperty);
return 0;
}
}
مثال 3-10 چگونگی ایجاد یک ویژگی فقط خواندنی را نشان میدهد. کلاس PropertyHolder دارای ویژگی SomeProperty است که فقط get accessor را پیادهسازی میکند. این کلاس PropertyHolder دارای سازندهایست که پارامتری از نوع int دریافت مینماید.
متد Main() از کلاس PropertyTester شیء جدیدی از PropertyHolder با نام propHold ایجاد مینماید. این نمونه از کلاس PropertyHolder از سازندة آن که مقداری صحیح را بعنوان پارامتر دریافت میکند، استفاده میکند. در این مثال این مقدار برابر با 5 در نظر گرفته میشود. این امر باعث تخصیص داده شدن عدد 5 به فیلد someProperty از شیء propHold میشود.
تا زمانیکه ویژگی SomeProperty از کلاس PropertyHolder فقط خواندنی است، هیچ راهی برای تغییر مقدار فیلد someProperty وجود ندارد. بعنوان مثال در صورتیکه عبارت propHold.SomeProperty = 7 را در کد برنامه اضافه نمایید، برنامة شما کامپایل نخواهد شد چراکه ویژگی SomeProperty فقط خواندنی است. اما اگر از این ویژگی در متد Console.WriteLine() استفاده نمایید بخوبی کار خواهد کرد زیرا این دستور تنها یک فرآیند خواندن است و با استفاده از get accessor این عمل قابل اجرا است.
ویژگیهای فقط نوشتنی (Write-Only Properties)
به مثال زیر توجه فرمایید :
مثال 4-10 : ویژگیهای فقط خواندنی
using System;
public class PropertyHolder
{
private int someProperty = 0;
public int SomeProperty
{
set
{
someProperty = value;
Console.WriteLine("someProperty is equal to {0}", someProperty);
}
}
}
public class PropertyTester
{
public static int Main(string[] args)
{
PropertyHolder propHold = new PropertyHolder();
propHold.SomeProperty = 5;
return 0;
}
}
مثال 4-10 چگونگی ایجاد و استفاده از ویژگی فقط نوشتنی را نشان میدهد. در این حالت get accessor را از ویژگی SomeProperty حذف کرده و به جای آن set accessor را قرار دادهایم.
متد Main() کلاس PropertyTester شیای جدید از همین کلاس با سازندة پیش فرض آن ایجاد مینماید. سپس با استفاده از ویژگی SomeProperty از شیء propHold، مقدار 5 را به فیلد someProperty مربوط به شیء propHold تخصیص میدهد. در این حالت set accessor مربوط به ویژگی SomeProperty فراخوانی شده و مقدار 5 را به فیلد someProperty تخصیص میدهد و سپس عبارت someProperty is equal to 5” “را در خروجی نمایش میدهد.
خلاصه
در این درس با ویژگیها آشنا شدید و نحوه استفاده از آنها را فرا گرفتید. روشهای کلاسیک کپسوله کردن از طریق استفاده از متدهای مجزا صورت میگرفت ولی با استفاده از ویژگیها (Property) میتوان به اجزای یک شیء همانند یک فیلد دسترسی پیدا کرد. ویژگیها را میتوان به صورت فقط خواندنی و یا فقط نوشتنی نیز ایجاد نمود. با استفاده از ویژگیها دسترسی مستقیم به فیلدهای مورد نظر از یک کلاس از بین رفته و این دسترسی تنها از طریق ویژگی مورد نظر امکانپذیر میگردد.