زنگ سی شارپ – قسمت چهل و پنجم

آشنایی با structure و enumeration در سی شارپ


مسعود درویشیان 15 دیدگاه سی شارپ Wednesday, 4th December , 2013 51511 بازدید

زنگ سی‌شارپ - قسمت چهل و پنجم

بین Interface و Abstract Class کدام‌یک را انتخاب کنیم؟

یکی از قسمت‌های مهم برنامه‌نویسی سی‌شارپ دانستن این موضوع است، هنگامی‌که قصد دارید قابلیت‌های یک کلاس را شرح دهید، چه زمانی از interface و چه زمانی از abstract class باید استفاده کنید درحالی‌که قسمت اجرایی ندارید. قانون کلی بدین صورت است که هرگاه بخواهید مفهوم کلی را شرح دهید و فقط به انجام شدن کارها تاکید داشته باشید و در واقع چگونه‌گی انجام شدن آن برای شما اهمیت نداشته باشد، باید از interface استفاده کنید. اگر نیاز دارید که بعضی از جزئیات اجرا شدن را از قبل وارد کنید، آن‌گاه باید abstract class را مورد استفاده قرار دهید.

Structures

همان‌طور که می‌دانید، کلاس‌هاreference type  هستند. این بدان معنا است که اشیای کلاس از طریق یک reference قابل دسترسی هستند. از این‌رو reference type ها با value type ها که مستقیماً قابل دسترسی‌اند، متفاوت هستند. اما دسترسی مستقیم به یک شیء (به شکلی مشابه با value type ها) نیز گاهی می‌تواند سودمند باشد. یکی از دلایل این‌کار، افزایش بهره‌وری است. درسترسی به اشیاء از طریق reference باعث به‌وجود آمدن overhead (سربار) می‌شود و این overhead ها فضا اشغال می‌کنند. برای هر شیء کوچک، این فضاها می‌تواند قابل توجه باشد. برای رفع این نگرانی، سی‌شارپ structure را ارائه داده است. یک structure مشابه با class است با این تفاوت که  structure، value type اما کلاس reference type است.

Structure ها توسط کلمه‌کلیدی struct تعریف می‌شوند و syntax آن‌ها مشابه class است. فرم کلی struct به شکل زیر است:

struct name : interfaces
{
    // member declarations
}

در این‌جا، اسم این structure  توسط name مشخص شده است. structure ها نمی‌توانند از structure ها یا از class های دیگر ارث‌بری کنند و همچنین نمی‌توان از آن‌ها برای structure ها و یا class های دیگر به عنوان base استفاده کرد. این بدین معنی است که inheritance در structure ها کاملاً بی استفاده است (اما به‌صورت پیش‌فرض structure از System.ValueType ارث‌بری می‌کند که System.ValueType خودش از object ارث‌بری می‌کند). با این حال یک structure می‌تواند یک یا چند interface را اجرا کند که این interface ها بعد از نام structure مشخص می‌شوند و لیست آن‌ها توسط کاما از هم جدا می‌شود. همچون کلاس، structure می‌تواند شامل اعضایی چون method، field، indexer، property، operator method و event باشد. structure ها همچنین می‌توانند constructor داشته باشند اما destructor ندارند. نکته‌ی دیگر این است که نمی‌توانید در structure ها default constructor (constructor بدون پارامتر) تعریف کنید زیرا default constructor به‌صورت پیش‌فرض برای همه‌ی structure ها تعریف شده است و این default constructor قابل تغییر نیست. Default constructor فیلدهای structure را به مقادیر پیش‌فرض‌شان مقداردهی می‌کند. از آن‌جایی که structure از inheritance پشتیبانی نمی‌کند، منطقی است که مجاز به استفاده از اعضای structure به‌عنوان abstract، virtual یا protected نخواهید بود.

یک شیء structure می‌تواند مشابه با کلاس با استفاده از new ساخته شود اما استفاده از new ضروری نیست. هنگامی‌که از new استفاده شود، constructor مشخص شده نیز فراخوانی خواهد شد. هنگامی‌که از new استفاده نشود، شیء ساخته می‌شود اما مقداردهی نشده است بنابراین نیاز است تا مقادیر آن را مقداردهی کنید.

به مثال زیر دقت کنید:

using System;
struct Gamepad
{
    public string name;
    public string color;

    public Gamepad(string name, string color)
    {
        this.name = name;
        this.color = color;
    }

    public void Show()
    {
        Console.WriteLine("Name : " + name);
        Console.WriteLine("Color: " + color);
    }
}
class MainClass
{
    static void Main()
    {
        Gamepad gamepad1 = new Gamepad("Xbox One Wireless Controller", "Black"); // explicit constructor

        Gamepad gamepad2 = new Gamepad(); // default constructor
        Gamepad gamepad3; // no constructor

        gamepad1.Show();

        Console.WriteLine();
        if (gamepad2.name == null)
            Console.WriteLine("gamepad2.name is null!");
        
        // Now, give gamepad2 some info
        gamepad2.name = "PS4 Wireless Controller";
        gamepad2.color = "Black";

        Console.WriteLine();
        Console.WriteLine("gamepad2 now contains: ");
        gamepad2.Show();

        Console.WriteLine();

        // Console.WriteLine(gamepad3.name); // must be initialize first

        gamepad3.name = "Steam Controller";
        gamepad3.color = "Gray";
        gamepad3.Show();
    }
}

/* Output
 
Name : Xbox One Wireless Controller
Color: Black

gamepad2.name is null!

gamepad2 now contains: 
PS4 Wireless Controller
Black

Name : Steam Controller
Color: Gray
 
*/

همان‌طور که برنامه‌ی بالا نشان می‌دهد، structure هم می‌تواند از طریق new و فراخوانی constructor ساخته شود و هم می‌تواند به‌سادگی فقط تعریف شود. اگر از new استفاده شود یا از default constructor و یا از constructor تعریف شده توسط برنامه‌نویس استفاده خواهد شد. اما اگر مانند gamepad3 از new استفاده نشود، پیش از استفاده از شیء بایستی حتماً آن را مقداردهی کنید.

هنگامی‌که یک structure را به یک structure دیگر اختصاص می‌دهید، یک کپی از شیء ساخته می‌شود. تفاوت مهم struct و class در همین نکته است. همان‌طور که قبلاً توضیح داده شد، هنگامی‌که یک class reference را به یک class reference دیگر اختصاص می‌دهید، reference سمت چپ تساوی به همان شیءای رجوع می‌کند که reference سمت راست تساوی به آن رجوع می‌کند. این بدان معنی است که اگر مقادیر شیء را تغییر دهید، هر دوی class reference ها تغییرات را می‌بینند. اما هنگامی‌که یک struct را به یک struct دیگر اختصاص می‌دهید، یک کپی مستقل از شیء ایجاد می‌کنید و تغییر یکی از اشیاء ربطی به دیگری ندارد.

به مثال زیر توجه کنید:

// Copy a struct.
using System;
// Define a structure.
struct MyStruct
{
    public int x;
}
// Demonstrate structure assignment.
class StructAssignment
{
    static void Main()
    {
        MyStruct a;
        MyStruct b;

        a.x = 10;
        b.x = 20;

        Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x);

        a = b;
        b.x = 30;

        Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x);
    }
}

/* Output
 
a.x 10, b.x 20
a.x 20, b.x 30
 
*/

همان‌طور که خروجی نشان می‌دهد، بعد از اختصاص

a = b;

structure variable های a و b  همچنان جدا و مجزا هستند. این بدان معناست که a به b رجوع نمی‌کند و ارتباطی بین آن‌ها نیست. فقط و فقط یک کپی مجزا از شیء b به a اختصاص داده شده است.

اگر a و b هردو class reference بودند، تغییراتی متفاوت با مثال بالا رخ می‌داد. به مثال زیر که class version مثال بالا است دقت کنید:

// Use a class.
using System;
// Now a class.
class MyClass
{
    public int x;
}
// Now show a class object assignment.
class ClassAssignment
{
    static void Main()
    {
        MyClass a = new MyClass();
        MyClass b = new MyClass();

        a.x = 10;
        b.x = 20;

        Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x);

        a = b;
        b.x = 30;

        Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x);
    }
}

/* Output
 
a.x 10, b.x 20
a.x 30, b.x 30
 
*/

همان‌طور که می‌بینید، a و b بعد از اختصاص‌دهی هردو یه یک شیء رجوع می‌کنند.

چرا باید از structure استفاده کنیم؟

ممکن است این سوال در ذهن‌تان به‌وجود آمده باشد که چرا در سی‌شارپ struct وجود دارد درحالی‌که کلاس ورژن کامل‌تری است. پاسخ این است که struct بهره‌وری و سرعت اجرای بیشتری دارد. دلیل آن این است که structure به‌صورت value type است و دیگر reference ای وجود ندارد بنابراین دسترسی به آن مستقیم است. از این‌رو در بعضی موارد از memory کمتری نیز استفاده می‌شود. درکل، هرگاه نیاز دارید که گروهی از اطلاعات مرتبط را کنار هم قرار دهید و در عین حال نیازی به inheritance و دسترسی به شیء از طریق reference ندارید، آن‌گاه struct می‌تواند انتخاب موثرتری باشد.

Enumerations

یک enumeration مجموعه‌ای از integer های نام‌گذاری شده است. نوع enumeration توسط کلمه‌کلیدی enum تعریف می‌شود. فرم کلی یک enumeration به شکل زیر است:

enum name { enumeration list };

در این‌جا، نام این enumeration توسط name مشخص شده است و enumeration list لیستی از شناسه‌ها است که توسط کاما از هم جدا شده‌اند.

در مثال زیر یک enumeration به اسم Apple وجود دارد که انواع مختلفی از Apple را لیست کرده است:

enum Apple
{
    Jonathan, GoldenDel, RedDel, Winesap,
    Cortland, McIntosh
};

نکته‌ی کلیدی در مورد enumeration ها این است که هر symbol در آن، جایگزین یک integer value است. دقت کنید که برای convert کردن بین این دو باید از explicit cast استفاده کنید. از آن‌جا که enumerations مقادیر integer را ارائه می‌دهند، می‌توانید از enumeration برای کنترل کردن (مثلاً) switch و حلقه‌ی for استفاده کنید.

شماره‌ی symbol های enumeration از صفر شروع می‌شود بنابراین در نمونه‌ی بالا، Jonathan مقدار صفر و GoldenDel مقدار یک دارد.

اعضای یک enumeration از طریق dot operator قابل دسترسی هستند. برای مثال:

Console.WriteLine(Apple.RedDel + " has the value " + (int)Apple.RedDel);

در خروجی، RedDel has the value 2 را نمایش می‌دهد.

همان‌طور که خروجی نشان می‌دهد، هنگامی‌که یک enumerated value نمایش داده می‌شود، از نام آن استفاده شده است. برای دیدن مقدار integer آن، باید از cast استفاده کرد.

به مثال زیر دقت کنید:

// Demonstrate an enumeration.
using System;
class EnumDemo
{
    enum Apple
    {
        Jonathan, GoldenDel, RedDel, Winesap,
        Cortland, McIntosh
    };
    static void Main()
    {
        string[] color = {
            "Red",
            "Yellow",
            "Red",
            "Red",
            "Red",
            "Reddish Green"
                         };
        Apple i; // declare an enum variable

        // Use i to cycle through the enum.
        for (i = Apple.Jonathan; i <= Apple.McIntosh; i++)
            Console.WriteLine(i + " has value of " + (int)i);

        Console.WriteLine();

        // Use an enumeration to index an array.
        for (i = Apple.Jonathan; i <= Apple.McIntosh; i++)
            Console.WriteLine("Color of " + i + " is " +
            color[(int)i]);
    }
}

/* Output
 
Jonathan has value of 0
GoldenDel has value of 1
RedDel has value of 2
Winesap has value of 3
Cortland has value of 4
McIntosh has value of 5

Color of Jonathan is Red
Color of GoldenDel is Yellow
Color of RedDel is Red
Color of Winesap is Red
Color of Cortland is Red
Color of McIntosh is Reddish Green
 
*/

دقت کنید که چگونه حلقه‌ی for با متغیر نوع Apple کنترل می‌شود. همان‌طور که گفته شد، هیچ‌گونه implicit conversion بین نوع integer و enumeration type وجود ندارد و در صورت نیاز حتماً باید از explicit cast استفاده کنید.

نکته‌ی دیگر این است که همه‌ی enumeration ها به‌طور پیش‌فرض از System.Enum ارث‌بری می‌کنند که System.Enum از System.ValueType و خود System.ValueType از object ارث‌بری می‌کند.

مقداردهی یک Enumeration

شما می‌توانید مقدار یک یا چند symbol را با استفاده از علامت تساوی و مقدار مورد نظرتان، مقداردهی کنید. بقیه‌ی symbol هایی که بعد از مقداردهی شما واقع هستند، به ترتیب مقدارشان یکی یکی بیشتر از مقداری که مشخص کرده‌اید می‌شود.

به نمونه‌ی زیر دقت کنید:

enum Apple
{
    Jonathan, GoldenDel, RedDel = 10, Winesap,
    Cortland, McIntosh
};

اکنون مقدار symbol ها به شکل زیر است:

/* Values of these symbols
 
Jonathan    =   0

GoldenDel   =   1

RedDel      =   10
  
Winesap     =   11

Cortland    =   12

McIntosh    =   13

*/

به‌صورت پیش‌فرض، enumeration ها بر اساس نوع int هستند اما شما می‌توانید یک enumeration با هر نوع عددی دیگری بسازید. برای این منظور باید به‌شکل زیر عمل کرد:

enum Apple : byte
{
    Jonathan, GoldenDel, RedDel, Winesap,
    Cortland, McIntosh
};

همان‌طور که می‌بینید، بعد از نام enumeration با استفاده از colon و مشخص کردن نوع byte، این کار را انجام داده‌ایم.

استفاده از Enumeration

ممکن است در نگاه اول با خود فکر کنید که enumeration بخشی از سی‌شارپ است که خیلی اهمیت ندارد اما در واقع این‌طور نیست و enumeration بسیار مفید و کاربردی است. برای مثال تصور کنید برنامه‌ای نوشتید که یک Conveyor را در یک کارخانه کنترل می‌کند و در برنامه‌ی شما متدی به نام ()Conveyor وجود دارد که فرمان‌های start، stop، forward و reverse را به‌عنوان پارامتر دریافت می‌کند. اکنون به‌جای فرستاندن مقادیر عددی (مثلاً ۱ برای start و ۲ برای stop و…) به این متد می‌توانیم از enumeration استفاده کنیم که به این مقادیر عددی یک کلمه اختصاص می‌دهد.

به مثال زیر دقت کنید:

// Simulate a conveyor belt.
using System;
class ConveyorControl
{
    // Enumerate the conveyor commands.
    public enum Action { Start, Stop, Forward, Reverse };

    public void Conveyor(Action com)
    {
        switch (com)
        {
            case Action.Start:
                Console.WriteLine("Starting conveyor.");
                break;
            case Action.Stop:
                Console.WriteLine("Stopping conveyor.");
                break;
            case Action.Forward:
                Console.WriteLine("Moving forward.");
                break;
            case Action.Reverse:
                Console.WriteLine("Moving backward.");
                break;
        }
    }
}
class ConveyorDemo
{
    static void Main()
    {
        ConveyorControl c = new ConveyorControl();

        c.Conveyor(ConveyorControl.Action.Start);
        c.Conveyor(ConveyorControl.Action.Forward);
        c.Conveyor(ConveyorControl.Action.Reverse);
        c.Conveyor(ConveyorControl.Action.Stop);
    }
}

/* Output
 
Starting conveyor.
Moving forward.
Moving backward.
Stopping conveyor. 

*/

به دلیل این‌که متد ()Conveyor یک argument از نوع Action می‌گیرد، فقط مقادیر مشخص شده در Action باید به آن فرستاده شوند. به‌عنوان مثال نمی‌توانید مقدار ۲۲ را به آن بدهید. به نحوه‌ی استفاده از enumeration برای کنترل دستور switch نیز دقت کنید.



نویسنده / مترجم : مسعود درویشیان

علاقه مند به موسیقی و برنامه نویسی بازی


15 دیدگاه برای این نوشته ثبت شده است


  1. رها
    4 December 2013

    سلام
    ممنون از سری آموزشهای شما
    خیلی کامل هستند
    گرچه من هنوز از سی شارپ چیزی نمیدونم و نمیفهمم اما علاقه شدیدی به asp.net دارم
    البته بیشتر به طراحی وب با این تکنولوژی
    امیدوارم اگر برای شما امکان داره بعد از اتمام این سری آموزشی یه آموزش طراحی وب با ASP.NET در همین انجمن ارائه کنید
    قطعا اون هم طرفداران زیادی خواهد داشت
    یکی از مشکلات من در مسیر یادگیری محل زندگیم هست که در یکی از بد ترین و دور افتاده ترین شهرها هستم که هیچگونه دسترسی به محل اموزش اینگونه زبانها ندارم متاسفانه
    واقعا ممنون
    برای شما دوست عزیز آرزوی توفیق روز افزون دارم
    راستی اگر اشکال نداره من این سری آموزشی شما را با ذکر منبع در یه انجمن دیگه باشتراک بزارم



    • تمام ASP.NET توی سی‌شارپ خلاصه می‌شه یعنی اگه شما سی‌شارپ رو خوب بلد بودید اون‌وقت ASP.NET رو هم راحت درک می‌کنید. در غیر این‌صورت به مشکل بر می‌خورید. پس سعی کنید سی‌شارپ رو خوب تمرین کنید.




  2. رها
    4 December 2013

    سلام
    ممنون از توضیحات و راهنمایی های شما
    در خصوص اجازه نشر آموزش شما در این انجمن نظرتون رو بیان نکردید
    آیا اجازه دارم سری کامل این آموزش را با ذکر منبع ولی باز نویسی شده و با تصوایر کاملتر در این بخش از انجمن که بتازگی راه افتاده و متعلق ه زبانهای برنامه نویسی هست قرار بدهم
    البته با ذکر منبع و پروفایل شما
    ممنون میشم اگر نظرتون رو بفرمایید که آیا اجازه چنین کاری رو دارم یا نه ؟
    با تشکر




  3. رها
    4 December 2013

    ممنونم
    قطعا با ذکر منبع و لینک و نام و پروفایل شخصی شما
    فقط بازنویسی و سعی میکنم با تصاویر زیباتر باشه




  4. سمانه
    5 December 2013

    سلام
    همونطور كه قبلا گفتم مثلا من سي شارپ پاس كردم…كه اي كاش پاس نميكردم…الان براي پروژه پايگاه داده م به مشكل خوردم براي اتصال sql serverبه c#واقعا نمي دونم چكاركنم…
    ممنون ميشم اگه راهنماييم كنيد…



    • این لینک رو ببینید.




      • سینا
        20 May 2014

        سلام دوست خوبم
        این لینکی که برای اون خانم فرستادید آموزش اتصال به دیتابیس بر اساس ADO هستش که دیگه منسوخ شده.
        بهتره از تکنولوژیهای جدیدتر مثل LINQ و NTT استفاده بشه تا از مشکلات احتمالی خصوصا در هنگام بروز رسانی و تغییرات در آینده جلوگیری بشه

        موفق باشید




  5. رها
    6 December 2013

    سلام
    اقای درویشیان وقتی روی تم کار میکنم و مثلا میخوام در یه فایل css ردیفی رو پیدا کنم چطور باید در ویژوآل استادیو اینکار رو بکنم .
    مثلا :
    من با فایر باگ روی یه بلاک کلیک میکنم و درپنل سمت راست فایر باگ میبینم که این قسمت که مد نظره منه در فایل x.css مثلا در خط 3456 هست
    حالا در ویژوآل استادیو بدون اینکه بخوام اسکرول کنم آیا راهی هست که بشه ردیف رو سرچ کرد؟
    من الان ردیف اعداد را روشن کردم و کنار خطها عدد هست امان میدونم چطور باید یه ردیف را سرچ کنم
    وقتی کلید های Ctrl+F رو میزنم سرچ باز میشه اما وقتی سرچ میکنم مثلا ردیف 3456 دورن کدها دنبال چنین عددی هست نه درون ردیف اعداد.
    ممنون میشم راهنمایی کنید



    • یه راهش اینه که ببینی اسم class یا id که واسه اون قسمت در نظر گرفته شده چی هست و همون اسم رو با ctrl+f توی vs سرچ کنی




      • رها
        7 December 2013

        سلام
        اقای درویشیان بالاخره پیداش کردم
        مینویسم تا اگر کسی دیگر هم مثل من نیاز داشت پیدا کنه
        برای پیدا کردن یک ردیف در فایلهای بلند و طولانی با گرفتن کلیدهای
        Ctrl+G
        و وارد کردن عدد مورد نظر به خط مورد نظر میریم
        از شما هم بابت راهنمایی هاتون واقعا سپاسگذارم



  6. سلام.
    من وب‌تارگت رو دنبال می‌کنم و به شما خسته نباشید می گم که این زنگ‌ها رو پیوسته و کامل منتشر می‌کنید. گرچه من نمی‌خونم ولی واقعا می‌دونم که کار سختی هست ساختن این آموزش‌ها و خسته نباشید می‌گم. سلامت و شاد باشید.
    با احترام.




  7. وزیری
    15 March 2014

    عالی بود دوست عزیز




  8. مبل بادی
    20 May 2014

    سلام.خیلی ممنون از آموزش دقیق و مفیدتون…امکان داره فیلم آموزشی هم بذارید؟؟



  9. سلام
    من چندتا entity دارم که همشون یک خاصیت دارند به نام OrderId که ترتیب قرار گیری فرضا محصولات یا تصاویر یا … رو مشخص میکند. یا یک ویژگی دیگر دارم که تقریبا همه entity ها دارند به اسم bool Enable که فعال بودن یا غیر فعال بودن اون رکورد رو مشخص میکنه.
    من نحوه تعریف واسط IEnumerator و IEnumerabel و ICollection و IList رو نگاه میکردم. فرضا بین واسط IList از IEnumerable ارث بری داره یعنی هر لیستی ویژگی شمارش پذیر بودن رو داره. تجرید رو به این شکل دیدم
    حالا
    میخوام برای entity های خودم تجرید داشته باشم. فرضا entity set ای که بین اون ها ترتیب مهم هست از یک واسط به اسم IOrderable ارث بری داشته باشه یا همینطور برای فعال یا غیرفعال بودن: IEnaableAble {نام گذاری هام مناسب نیست برای این دو نام گذاری بهتری سراغ دارید}
    interface IOrderable{
    public int OrderId {set;get;}
    //
    }

    class Produtct : IOrderable
    {

    }

    سوالم اینجاست که تجرید رو به چه شکلی میتونم داشته باشم فرضا برای orderId که در همه entity ها تکرار شده.
    داخل پرانتز یک نکته ای رو که متوجه شدم در مورد شی گرایی و تجرید ، به تجربه به دست اوردم این هست که، اگر منطق شی گرایی پشت تجرید نباشه اشکال به وجود میاد یعنی صرف اینکه من بخواهم تنها از تکرار جلوگیری کنم و از یک سری ویژگی ها فاکتور گیری کنم و تجرید رو پیاده سازی کنم بیام یه کلاس پدر درست بکنم و بقیه از این کلاس ارث بری داشته باشند بدون منطق و مفهوم ورابطه ای بین این پدر فرزند بعدا دچار مشکل میشم.
    اما اینجا واقعا رابطه منطقی وجود دارده درواقع میخوام به یک entity قابلیت ترتیب پذیر بودن رکورد ها یا فعال یا غیر فعال بودن(یک flag ) اضافه بکنم. تجرید رو چطور پیاده سازی بکنم؟
    اقا من خیلی سوالات اینطوری دارم. کی تماس بگیرم فرصت باشه بتونیم صحبت بکنیم.
    با تشکر



دیدگاه خود را بنویسید





نشانی ایمیل شما منتشر نخواهد شد.

کامنت های شما بعد از تأیید توسط نویسنده وبلاگ، منتشر خواهند شد.

لطفا دیدگاهتان تا حد امکان مربوط به پست بالا باشد. اگر حرف دیگری دارید و یا قصد تماس با من را دارید، از فرم تماس استفاده کنید.

شما میتوانید با مراجعه به سایت گراواتار یک آواتار اختصاصی برای خود تعریف کنید، تا در کنار نام شما نمایش داده شود

برای قرار دادن کدهای نمونه می توانید از تگ های [php] ، [html] ، [css] و [js] استفاده کنید.
به عنوان مثال کدهای php را می توان به صورت زیر قرار داد:
[php] var $whoLoveIranians = "WebTarget!"; [/php]



کلیه حقوق مادی و معنوی برای وب سایت وب تارگت محفوظ است ©2024 وب‌تارگت

استفاده از مطالب وب سایت در سایر وب سایت‌ها و نشریات چاپی با ذکر منبع آزاد است.