درس دوازدهم – ساختارها در 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 نگهداری میشوند. استفاده از ساختارها همانند سایر انواع مقداری است.