درس هفدهم : انواع شمارشی در C#

درس هفدهم : انواع شمارشی در C#

 در این درس با انواع شمارشی (Enumerator Types) در زبان C# آَشنا خواهیم شد. مطالب مورد بررسی در این درس به شرح زیر می‌باشند :

 

درک و فهم یک نوع شمارشی یا یک enum

ساخت یک نوع شمارشی جدید

چگونگی استفاده از انواع شمارشی

آشنایی با متدهای مختلف موجود در System.Enum 

enmu فرم خاصی از انواع مقداری (Value Type) است که از System.Enum مشتق شده و امکان پیمایش درون مجموعه‌ای مشخص را با استفاد از اعداد صحصیح برای ما فراهم می‌نماید. با استفاده از enum می‌توان مجموعه‌ای از مقادیر ثابت را تعریف نمود که این مقادیر ثابت با استفاده از یک عدد صحیح قابل دسترسی هستند.

 استفاده از enum در برنامه‌ها باعث بالا رفتن خوانایی برنامه می‌شود، چراکه با استفاده از آنها می‌توان با مجموعه‌ای از اعداد صحیح ترتیبی (Sequential) ، با عناوینی تعریف شده، کار کرد. برای مثال، در اعلان یک enum، ما مجموعه‌ای از نامهای مورد نظر را تعریف می‌نماییم و در برنامه می‌توانیم از این نامها بصورت ترتیبی استفاده نماییم. Enum به هر یک از عناصر موجود در این مجموعه عددی را تخصیص می‌دهد که شروع این عدد می‌تواند توسط برنامه‌نویس نیز معین گردد. سپس با استفاده از نام عناصر موجود در enum و یا با استفاده از اعدادی که به هر یک از این عناصر تخصیص داده شده، می‌توان enum را پیمایش نمود و به عناصر آن دسترسی داشت.

 همانطور که گفته شد، enum یک نوع مقداری (Value Type) است، از اینرو ارث‌بری در مورد آن معنایی ندارد. مساوی قرار دادن دو enum نیز، مقادیر یکی را در دیگری کپی می‌کند. همانطور که در این درس، و در منابع دیگر، خواهید یافت، دسترسی به انواع شمارشی در C# با استفاده از دو کلمه enum و Enum امکان پذیر است. در C# نوع شمارشی enum از نوع BCL خود یعنی Enum ارث‌بری می‌کند ! با استفاده از enum یک نوع شمارشی جدید تولید می‌شود و با استفاده از Enum، می‌توان به پیاده‌سازی متدهای استاتیک انواع شمارشی پرداخت.

 ایجاد یک نوع شمارشی

.Net Framework BCL حاویenum ها و مثالهای متعددی از استفادة آنها می‌باشد. برای مثال هرگاه که از MessageBox بر روی فرمی استفاده می‌شود، می‌توان از MessageBoxIcon که یک نوع شمارشی است استفاده نمود.

 علاوه بر انواع شمارشی تعریف شده و موجود در .Net Framework، زمانهایی نیز وجود دارند که می‌خواهیم مجموعه‌ای از عناصر را تعریف کرده و از آنها استفاده نماییم. برای دسترسی به عناصری از نوع صحیح، استفاده از enum باعث خوانا شدن برنامه می‌گردد.

نحوه اعلان یک enum در حالت کلی بصورت زیر است :

enum

            {

                        // Enumeration list

            {

 در مثال 1-17 که در زیر ملاحظه می‌کنید، نحوه اعلان و استفاده از enum مشخص شده است.

 مثال 1-17 : نحوه اعلان یک enum

using System;

// declares the enum
public enum Volume
{
   Low,
   Medium,
   High
}

// demonstrates how to use the enum

class EnumSwitch
{
   static void Main()
   {
      // create and initialize 
      // instance of enum type

      Volume myVolume = Volume.Medium;

      // make decision based
      // on enum value
      switch (myVolume)
      {
         case Volume.Low:
            Console.WriteLine("The volume has been turned Down.");
            break;
         case Volume.Medium:
            Console.WriteLine("The volume is in the middle.");
            break;
         case Volume.High:
            Console.WriteLine("The volume has been turned up.");
            break;
      }
      Console.ReadLine();
   }
}

 در مثال 1-17 نمونه‌ای از اعلان یک enum را مشاهده می‌نمایید. همانطور که دیده می‌شود، اعلان یک نوع شمارشی با استفاده از کلمه کلیدی enum صورت گرفته و سپس به دنبال آن نام این مجموعه مشخص می‌شود. درون کروشه‌های باز و بسته { }   نیز، عناصر نوع شمارشی اعلان می‌گردند.

 نوع شمارشی تولید شده در این مثال از نوع Volume است و در متد Main() از آن برای اعلان myVolume استفاده شده است. از آنجائیکه enum یک نوع مقداری است، می‌توانیم بطور مستقیم آنرا مقداردهی نماییم. پس از آنکه متغیر myVolume مقداردهی شد، می‌توان همانند سایر انواع مقداری، مانند int، از آن استفاده نمود. در حلقه switch، متغیر myVolume با عناصر enum مقایسه می‌شوند.

 در هربار استفاده از عناصر enum تولید شده، از نام enum تولید شده، در اینجا Volume، در ابتدای نام عنصر استفاده می‌نماییم (Volume.Medium)، زیرا در صورتیکه در یک برنامه چندین enum وجود داشته باشند که دارای عناصری با نامهای یکسان باشند، در صورت عدم استفاده از نام enum مورد نظر قبل از عنصر، ابهام بوجود آمده و دچار مشکل می‌شویم.

 بطور پیش فرض، به اولین عنصر تعریف شده در enum مقدار صفر تخصیص داده می‌شود که این مقدار تنها بعنوان اندیسی جهت دسترسی به این عنصر در نظر گرفته می‌شود. سایر عناصر enum نیز بطور صعودی مقدار می‌گیرند و به هر عنصر یک واحد افزوده می‌شود. در مثال 1-17، عنصر Low دارای اندیس صفر بوده و سایر عناصر به ترتیب مقدار 1 و 2 خواهند داشت.

 در C#، برای موارد خاص می‌توان مقادیر پیش فرض در نظر گرفته شده برای عناصر enum را تغییر داد. به مثال زیر توجه کنید.

 

enum Months

     {

       jan, feb, mar, apr

     }

 

enum Months

     {

       jan = 10, feb = 20, mar = 30, apr=40 

     } 

 همنطور که مشاهده می‌شود، در اعلان اول، از مقدار پیش فرض استفاده شده، که در این حالت jan = 0، feb = 1، mar = 2 و apr = 3 خواهند بود. اما در اعلان دوم، برنامه‌نویس بنا به نیاز خود، تشخیص داده تا به هر یک از عناصر enum مقداری دلخواه را نسبت دهد.

 هر چند به تمامی عناصر enum مقداری نسبت داده می‌شود ولی از این مقدار نمی‌توان بطور مسقیم در تخصیص دهی مقدار به متغیری دیگر استفاده نمود. به مثال زیر توجه نمایید :

int x = Months.jan;// این دستور نادرست است

int x = (int) Months.jan ; //صحیح

برای استفاده از مقدار تخصیص داده شده به عناصر enum، باید از Casting استفاده نماییم. بدین معنا که باید نوع متغییری را که می‌خواهیم مقدار را به ان نسبت دهیم، باید مشخص شود. در مثال فوق (int) Months.jan معین می‌کند که مقدار تخصیص داده شده به jan به متغییری نسبت داده می‌شود که از نوع int است و یا به عبارت صحیح تر، مقدار تخصیص داده شده به عنصر enum، در فرمت int به متغییر مورد نظر تخصیص داده می‌شود.

 در ادامه مبحث، توجه شما را به مثالی دیگر درباره enum جلب می نمایم. توجه نمایید که نکات جدیدی نیز در این مثال گنجانده شده اند.

 مثال 2-17 : ساخت enum پایه و تخصیص دهی اعضای آن

 

using System;

 // declares the enum

public enum Volume : byte

{

    Low = 1,

    Medium,

    High

}

 

class EnumBaseAndMembers

{

    static void Main()

    {

        // create and initialize

        // instance of enum type

        Volume myVolume = Volume.Low;

         // make decision based

        // on enum value

        switch (myVolume)

        {

            case Volume.Low:

                Console.WriteLine("The volume has been turned Down.");

                break;

            case Volume.Medium:

                Console.WriteLine("The volume is in the middle.");

                break;

            case Volume.High:

                Console.WriteLine("The volume has been turned up.");

                break;

        }

        Console.ReadLine();

    }

}

 

با توجه به مثال 2-17 با نحوه تغییر نوع پایه یک enum آشنا می شوید. همانطور که ملاحظه می نمایید، نوع پایه این enum به byte تغییر یافته است. این امر بیان میدارئ که تنها مقادیری از نوع byte قابل تخصیص به عناصر enum هستند.

 همانطور که قبلا نیز اشاره شد، مقدار پیش فرض برای اولین عضو enum یعنی Low برابر با صفر است. چون در این مثال مقدار Low را برابر با یک قرار داده ایم، از اینرو مقادیر دو عضو دیگر آن نیز بصورت Middle=2 و High=3 تغییر خواهند یافت.

 نکات پیشرفته درباره enum

 در زبان C# هر نمونه از enum فضایی معادل با 4 بایت از حافظه را اشغال می نمایند. این مطلب با استفاده از کلمه کلیدی sizeof قابل بررسی لسا. از اینرو از enum میتوان به عنوان یک ساختمان داده مناسب و کارا یاد کرد.

نکته بسیار مهم و جالب در مورد enum در زبان برنامه نویسی C# انست که، برای هر کلاس enum موجود در کد برنامه، مقادیر رشته ای تخصیص داده شده به عناصر enum در یک اسمبلی و بصورت Metadata ذخیره می گردند، از اینرو دسترسی به این مقادیر رشته ای در کد میسر می شود. همچنین می توان از متدهای مختلف مرتبط با enum نیز استفاده نمود. به مثال ساده زیر توجه نمایید :

 

enum Language

{

    CSharp,MCpp,VBNet,JScript,IL

}

class App

{

    public static void Main()

    {

        Console.WriteLine("Write the number of the selected Language");

        string[] langAr = Enum.GetNames(Type.GetType("Language"));

        for(int i=0;i

        {

            Console.WriteLine(i + "." + langAr[i]);

        }

        Language myLang=(Language)Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Your Language of choice is: " + myLang);

    }

}

 


C# Tutorial For Beginners

Author : Joe Mayo

Copyrights © 2001-2003

http://www.csharp-station.com/

 

Enums Powered By Reflection

Author : JSLA

Copyrights © 2003 Jose Luis Sampayo Aller

Copyrights © 2003 The Code Project

http://www.codeproject.com/

 

Enumerators in C#

Author : Rajesh V. S.

Copyrights © 2001 Rajesh V. S.

Copyrights © 2001 C# Corner

http://www.c-sharpcorner.com/

ادامه درس شانزدهم

ادامه درس شانزدهم

 تعریف و یا کنترل موارد استفاده از یک صفت

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

 این کلاس دارای سه property مختلف است که می‌توان آنها را به هنگام استفاده صفت شخصی تنظیم نمود و مورد استفاده قرار داد.

 ValidOn

با استفاده از این property می‌توانیم مشخص کنیم که صفت تولید شده توسط ما، بر روی کدام یک از عناصر برنامه قابل اعمال هستند. اطلاعات این عناصر از AttributeTarget گرفته می‌شود و می‌توان عناصر مختلف را بوسیله OR بیتی با یکدیگر ترکیب نمود.

 AllowMultiple

با استفاده از این property می‌توان مشخص کرد که آیا می‌توان از این صفت بیش از یکبار بر روی یک عنصر برنامه استفاده کرد یا نه.

 Inherited

با استفاده از این property می‌توان قوانین ارث‌بری این صفت را کنترل نمود. با استفاده از این property می‌توان مشخص کرد که آیا کلاسی که از کلاسی که صفت بر روی آن اعمال شده، ارث بری می‌کند نیز، صفت بر رویش اعمال می‌شود یا نه و یا به عبارتی صفت در کلاس مشتق شده نیز مورد ارث‌بری قرار می‌گیرد یا نه.

 حال با استفاده از موارد گفته شده در بالا، می‌خواهیم این مطالب را بر روی صفتی که خودمان تولید کردیم اعمال نماییم. مثال 7-16 را بررسی نمایید.

 مثال 7-16

using System;
[AttributeUsage(AttributeTargets.Class), AllowMultiple = false, Inherited = false ]
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Description_in)
    {
        this.description = Description_in;
    }
    protected String description;
    public String Description
    {
        get 
        {
            return this.description;
        }            
    }    
}

 

در ابتدا به AttributeTargets.Class توجه نمایید. این مشخص می‌کند که صفت Help تنها بر روی کلاسها قابل اعمال است و در صورتیکه از آن بر روی عنصری به غیر از کلاس استفاده نماییم خطایی رخ خواهد داد. بنابراین کد زیر، خطایی تولید خواهد کرد :

 

[Help("this is a do-nothing class")]
public class AnyClass
{
    [Help("this is a do-nothing method")]    //error
    public void AnyMethod()
    {
    }
} 

و کد خطای تولید شده بشکل زیر خواهد بود :

 

AnyClass.cs: Attribute 'Help' is not valid on this declaration type. 
It is valid on 'class' declarations only.

 

توجه کنید که با استفاده از AttributeTargets.All به صفت Help این امکان را می‌دهیم تا بر روی تمامی عناصر موجود اعمال شود. لیست کامل عناصر مجاز نیز بشرح زیر است :

  • Assembly, 
  • Module, 
  • Class, 
  • Struct, 
  • Enum, 
  • Constructor, 
  • Method, 
  • Property, 
  • Field,
  • Event, 
  • Interface, 
  • Parameter, 
  • Delegate, 
  • All = Assembly , Module , Class , Struct , Enum ,  Constructor , Method , Property , Field , Event , Interface , Parameter , Delegate,
  • ClassMembers = Class , Struct , Enum , Constructor , Method , Property , Field , Event , Delegate , Interface

 حال به AllowMultiple = false توجه نمایید. با استفاده از این کد، به صفت Help اجازه می‌دهیم تا تنها یکبار بر روی عنصری از برنامه اعمال شود. پس کد زیر تولید خطا می‌نماید :

 

[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
    [Help("this is a do-nothing method")]        //error
    public void AnyMethod()
    {
    }
}

 

و کد خطای تولید شده نیز بصورت زیر است :

 

AnyClass.cs: Duplicate 'Help' attribute

 

در نهایت نیز به بررسی Inherited می‌پردازیم. با استفاده از این ویژگی، معین می‌کنیم درصورتیکه کلاس دیگری بخواهد از روی کلاسی که صفت بر روی آن اعمال شده ارث‌بری نماید، آیا این صفت بر روی آن کلاس نیز اعمال شود یا نه. در صورتیکه مقدار این ویژگی برابر با True باشد، کلاس مشتق شده نیز از صفت ارث‌بری می‌نماید. برای یک مثال می‌توانیم حالت زیر را در نظر بگیریم :

 

[Help("BaseClass")] 
public class Base
{
}
 
public class Derive :  Base
{
}

 

تمامی حالتهای مختلف ترکیب این سه ویژگی بصورت زیر است :

 

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ]

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ]

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ]

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]

 استفاده از پارامترهای Positional و Named در صفتهای شخصی

 همانطور که در قبل نیز اشاره شد، پارامترهای Positional پارامترهای سازنده صفت هستند و در هر بار استفاده از صفت باید لحاظ شوند. حال برای بررسی می‌خواهیم پارامترهایی به صفت Help خود اضافه نماییم.

 مثال 8-16

 

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Description_in)
    {
        this.description = Description_in;
        this.verion = "No Version is defined for this class";
    }
    protected String description;
    public String Description
    {
        get 
        {
            return this.description;
        }
    }
    protected String version;
    public String Version
    {
        get 
        {
            return this.version;
        }
        //if we ever want our attribute user to set this property, 
        //we must specify set method for it 
        set 
        {
            this.verion = value;
        }
    }
}
[Help("This is Class1")]
public class Class1
{
}
 
[Help("This is Class2", Version = "1.0")]
public class Class2
{
}
 
[Help("This is Class3", Version = "2.0", Description = "This is do-nothing class")]
public class Class3
{
}

 

پس از اینکه این صفت را بر روی کلاس Class1 اعمال کردیم و بخواهیم آنرا کامپایل کنیم با پیغام زیر روبرو می‌شویم :

 

Help.Description : This is Class1
Help.Version :No Version is defined for this class

 

چون در اینجا هیچ مقداری برای Version در نظر نگرفته‌ایم، با این پیام مواجه شده‌ایم.

 حال نتیجه اعمال این صفت را بر روی کلاس دوم بررسی می‌کنیم.

 

Help.Description : This is Class2
Help.Version :  1.0

 

برای پارامترهای اختیاری معمولا از دو سازنده استفاده نمی‌شود و در عوض از پارامترهای Named استفاده می‌گردد. نکته‌ای که باید به آن توجه کنید آنست که برای پارامترهای Named حتما باید در تعریف property، از متد set نیز استفاده نمایید در غیر اینصورت با پیغام خطای زیر روبرو می‌شوید :

 

'Version' : Named attribute argument can't be a read only property

 

بنابراین درصورتیکه این صفت را بر روی کلاس سوم نیز اعمال کنیم با پیغام خطای مشابهی روبرو خواهیم شد. اگر در کلاس Help تغییری کوچکی اییجاد کنیم و به Description نیز متد set را بیفزاییم، با خطا مواجه نخواهیم شد.

 

Help.Description : This is do-nothing class 
Help.Version : 2.0

 

اتفاقی که در اینجا  رخ می‌دهد آنست که در ابتدا سازنده (Constructor) این صفت به همراه پارامترهای Positional آن فراخوانده می‌شوند و سپس متد set برای هر یک از پارامترهای Named فراخوانده می‌شود .

 انواع (type) معتبر برای پارامترهای صفت

 انواع معتبر برای پارامترهای صفت بشرح زیر می‌باشند :

bool,byte,char,double,float,int,long,short,string,System.Type ,object 

 

همچنین می‌توان از enum و یا آرایه‌ای تک بعدی، که عناصر آن یکی از انواع فوق باشد، نیز استفاده نمود.

 استفاده از صفتها در زمان اجرا

تا کنون با طریقه ساخت صفتها و چگونگی استفاده و اعمال آنها بر عناصر مختلف برنامه آشنا شدیم. حال نوبت به آن رسیده است تا ببینیم چگونه می‌توان از صفتها در زمان اجرا استفاده نمود. برای جستجوی (query) یک برنامه درباره صفت موجود در آن، به Reflection نیازمندیم. Reflection قابلیت بدست آوردن اطلاعات مربوط به انواع (Types) مختلف در زمان اجرای برنامه است. با استفاده از توابع Reflection موجود در .Net Framework می‌توانیم با جستجو و پیمایش Metadate مربوط به یک اسمبلی، لیست کاملی از کلاسها، انواع و متدهایی را که برای آن اسمبلی خاص تعریف شده‌اند را، بدست آوریم. به مثال 9-16 در این باره توجه نمایید.

 مثال 9-16 : استفاده از Reflection

 

using System;

using System.Reflection;

using System.Diagnostics;

 

//attaching Help attribute to entire assembly

[assembly : Help("This Assembly demonstrates custom attributes creation and their run-time query.")]

 

//our custom attribute class

public class HelpAttribute : Attribute

{

    public HelpAttribute(String Description_in)

    {

        //

        // TODO: Add constructor logic here

        this.description = Description_in;

        //

    }

    protected String description;

    public String Description

    {

        get

        {

            return this.deescription;

                

        }           

    }   

}

//attaching Help attribute to our AnyClass

[HelpString("This is a do-nothing Class.")]

public class AnyClass

{

//attaching Help attribute to our AnyMethod

    [Help("This is a do-nothing Method.")]

    public void AnyMethod()

    {

    }

//attaching Help attribute to our AnyInt Field

    [Help("This is any Integer.")]

    public int AnyInt;

}

class QueryApp

{

    public static void Main()

    {

    }

}

 

مبحث صفتها بسیار گسترده است و می‌توان ساعتها در مورد صفتهای مختلف بحث نمود. اما آنچه مسلم است تمرین مستمر و پیگیری برنامه نویس در یافتن مواردی که می‌توان با استفاده از صفتها، برنامه‌ای پویاتر ایجاد نمود، مهمترین عامل در فهم و درک کامل مبحث خواهد بود. درباره Reflection نیز در آینده در یان سایت مفصلا توضیح خواهم داد.

 خلاصه :

در این درس با صفتها آشنا شدید. یاد گرفتید که چگونه از صفتهای موجود در .Net Framework استفاده کرده و همچنین چگونه صفتهای شخصی و دلخواه خود را تولید نمایید. همچنین با پارامترهای صفتها و انواع آنها و نیز، عناصری که صفتها بر روی آنها اعمال می‌شوند آشنا شدید. در انتهای درس نیز به مختصر درباره Reflection و چگونگی استفاده از صفتها در زمان اجرا صحبت کردیم. امیدوارم مفید واقع شده باشد.

درس شانزدهم – استفاده از صفتها در C#

 درس شانزدهم – استفاده از صفتها در C#

 در این درس با نحوه استفاده از صفتها در زبان C# آشنا خواهید شد. اهداف ما در این درس به شرح زیر است :

1-     صفتها چه هستند و چرا از آنها استفاده می‌کنیم

2-     استفاده از صفتهای تک پارامتری و چند پارامتری

3-   انواع پارامترهای صفت (پارامترهای Named و Positional)

4- Target های صفتها (عناصری که صفتها بر روی آنها اعمال می‌شوند)

5- تولید صفتهای شخصی

6- تعریف و یا کنترل موارد استفاده از یک صفت

7- استفاده از پارامترهای Positional و Named در صفتهای شخصی

8- انواع (type) معتبر برای پارامترهای صفت

9- استفاده از صفتها در زمان اجرا

10- خلاصه مطالب

11- منابع
 

صفتها در حقیقت اطلاعات توضیحی هستند که می‌توانید آنها را به برنامه‌های خود بیفزایید. صفتها را می‌توان برای کلیه عناصر برنامه از قبیل کلاسها، واسطها، اسمبلی ها و ... مورد استفاده قرار داد. از این اطلاعات می‌توان برای موارد متنوعی در زمان اجرای برنامه استفاده نمود. برای مثال می‌توان به صفتی مانند DllImportAttribute اشاره کرد که امکان برقراری ارتباط با توابع کتابخانه‌ای Win32 را فراهم می‌نماید. همچنین صفتهایی نیز وجود دارند که برنامه‌نویس یا توسعه دهنده برنامه را در امر تولید برنامه یاری می‌نمایند. برای مثال می‌توان به صفت ObsoleteAttribute اشاره کرد که با استفاده از آن، در زمان کامپایل برنامه پیغامی برای برنامه نویس نمایش داده می‌شود و مشخص می‌کند که متدی خاص مورد استفاده قرار نگرفته و یا دیگر مورد استفاده نیست. همچنین هنگامیکه با فرمهای ویندوز کار می‌کنیم، صفتهای بسیاری وجود دارند که امکان استفاده از این فرمها را فراهم کرده و باعث می‌شوند تا اطلاعات مربوط به این عناصر در property فرم ظاهر شوند. یکی دیگر از موارد استفاده از صفتها در مسایل امنیتی اسمبلی‌های .Net است. برای مثال صفتهایی وجود دارند که باعث جلوگیری از فراخوانی‌های غیر مجاز می‌شوند، بدین معنی که تنها اجازه فراخوانی را به متدها یا اشیایی می‌دهند که قبلا تعریف شده و مشخص شده باشند.

 یکی از علتهای استفاده از صفتها آنست که، اغلب سرویسهایی را که آنها برای کاربر فراهم می‌نمایند، بسیار پیچیده است و با کدهای معمولی نمی‌توان آنرا را بدست آورد. از اینرو استفاده از صفتها در بسیاری از موارد ضروری و اجتناب ناپذیر است. همانطور که خواهید دید، صفتها به برنامه‌های ما Metadata اضافه می‌نمایند. پس از کامپایل برنامه‌های C#، فایل اسمبلی برای آن ایجاد می‌گردد که این اسمبلی معمولا یا یک فایل اجرایی است و یا یک Dll است. توصیف اسمبلی، در Metadata  ی مربوط به آن قرار می‌گیرد. طی پروسه‌ای تحت عنوان Reflection، صفت یک برنامه از طریق فایل Metadata  ی موجود در اسمبلی آن قابل دسترس می‌گردد. .(برای آشنایی بیشتر با اسمبلی و Metadata می‌توانید به " کامپایل یک برنامه سی شارپ " در همین سایت مراجعه نمایید.) در حقیقت صفتها، کلاسهایی هستند که می‌توانید آنها را با زبان C# تولید کرده و جهت افزودن اطلاعاتی توضیحی به کد خود، از آنها استفاده نمایید. این اطلاعات در زمان اجرای برنامه از طریق Reflection قابل دسترسی هستند.

 در این درس با روش استفاده از صفتها و چگونگی ارتباط دادن آنها با عناصر مختلف برنامه آشنا خواهید شد.

 مفاهیم اولیه درباره صفتها

 صفتها را معمولا قبل از اعلان عنصر مورد نظر در برنامه قرار می‌دهند. اعلان صفتها بدین صورت است که نام صفت درون دو براکت قرار می‌گیرد.

[ObsoleteAttribute]

استفاده از کلمه Attribute در اعلان صفت الزامی نیست، از اینرو اعلان زیر با اعلان فوق یکسان است :

[Obsolete]

همچنین صفتها می‌توانند دارای پارامتر نیز باشند که با استفاده از آنها خواص بیشتری را در اختیار برنامه قرار می‌دهند. در مثال 1-16 موارد متنوعی از استفاده صفت ObsoleteAttribute را مشاهده می‌نمایید.

 

مثال 1-16 :‌ نحوه استفاده از صفتها

using System;

class BasicAttributeDemo
{
    [Obsolete]
    public void MyFirstDeprecatedMethod()
    {
        Console.WriteLine("Called MyFirstDeprecatedMethod().");
    }

    [ObsoleteAttribute]
    public void MySecondDeprecatedMethod()
    {
        Console.WriteLine("Called MySecondDeprecatedMethod().");
    }

    [Obsolete("You shouldn't use this method anymore.")]
    public void MyThirdDeprecatedMethod()
    {
        Console.WriteLine("Called MyThirdDeprecatedMethod().");
    }

    // make the program thread safe for COM
    [STAThread]
    static void Main(string[] args)
    {
        BasicAttributeDemo attrDemo = new BasicAttributeDemo();

        attrDemo.MyFirstDeprecatedMethod();
        attrDemo.MySecondDeprecatedMethod();
        attrDemo.MyThirdDeprecatedMethod();
    }
}

 

همانطور که در مثال 1-16 نیز مشاهده می‌شود، صفت Obsolete در فرمهای مختلف مورد استفاده قرار گرفته است. اولین محلی که از این صفت استفاده شده است، متد MyFirstDeprecatedMethod() و پس از آن در متد MySecondDeprecatedMethod()  است. تنها تفاوت استفاده در این دو حالت آنست که در متد دوم صفت با نام کامل یعنی به همراه کلمه Attribute مورد استفاده قرار گرفته است. نتیجه هر دو اعلان یکسان است. همانطور که گفته بودیم، صفتها می‌توانند دارای پارامتر نیز باشند :

[Obsolete("You shouldn't use this method anymore.")]

    public void MyThirdDeprecatedMethod()

    ...

این پارامتر، ویژگی خاصی را به صفت می‌افزاید که آن را با دو اعلان قبلی متمایز می‌نماید. نتیجه هر سه اعلان این صفت در زیر آورده شده است. این پیغامها، پیامهای کامپایلر C# هستند که به هنگام کامپایل برنامه تولید شده‌اند.

 

>csc BasicAttributeDemo.cs
Microsoft (R) Visual C# .NET Compiler version 7.10.2292.4
for Microsoft (R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.
 
BasicAttributeDemo.cs(29,3): warning CS0612:
'BasicAttributeDemo.MyFirstDeprecatedMethod()' is obsolete
BasicAttributeDemo.cs(30,3): warning CS0612:
'BasicAttributeDemo.MySecondDeprecatedMethod()' is obsolete
BasicAttributeDemo.cs(31,3): warning CS0618:
'BasicAttributeDemo.MyThirdDeprecatedMethod()' is obsolete: 'You shouldn't use this method anymore.'

 

همانطور که ملاحظه می‌کنید، سومین اعلان صفت در این برنامه که با پارامتر همراه بود، باعث شده است تا پارامتر صفت نیز به عنوان بخشی از پیام نمایش داده شده توسط کامپایلر، نشان داده شود. در مورد دو صفت دیگر نیز مشاهده می‌شود که تنها پیغامی ساده تولید گردیده است.

 مثال 1-16 شامل صفت دیگری نیز می‌باشد. این صفت STAThreadAttribute است که معمولا در ابتدای کلیه برنامه‌های C# و قبل از آغاز متد Main() قرار می‌گیرد. این صفت بیان می‌دارد که برنامه C# مورد نظر می‌تواند با کد مدیریت نشده COM از طریق Simple Threading Apartment ارتباط برقرار نماید. استفاده از این صفت در هر برنامه‌ای می‌تواند مفید باشد، چراکه شما بعنوان برنامه نویس هیچ‌گاه اطلاع ندارید که آیا کنابخانه ثالثی که از آن استفاده می‌کنید، قصد برقراری ارتباط با COM را دارد یا نه؟ (در صورتیکه با برخی از اصطلاحات بکار رفته آشنایی ندارید اصلا نگران نشوید. در اینجا هدف تنها نشان دادن موارد استفاده از صفتهاست.)

 صفتها می‌توانند دارای چندین پارامتر باشند. در مثال 2-16، استفاده از دو پارامتر برای یک صفت نشان داده شده است.

 مثال 2-16

using System;
public class AnyClass 
{
    [Obsolete("Don't use Old method, use New method", true)]
    static void Old( ) { }
   
    static void New( ) { }
   
    public static void Main( ) 
    {
        Old( );
    }
}

همانطور که در مثال 2-16 مشاهده می‌کنید، صفت مورد استفاده دارای دو پارامتر است. پارامتر اول که یک جمله متنی است و همانند مثال 1-16 عمل می‌کند. پارامتر دوم نیز بیان کننده نوع پیغامی است که این صفت در هنگام کامپایل تولید می‌کند. در صورتیکه این مقدار برابر با True باشد، بدین معناست که در هنگام کامپایل پیغام خطا تولید می‌شود و کامپایل برنامه متوقف می‌گردد. در حالت پیش فرض مقدار این پارامتر برابر با False است که بیان می‌دارد، به هنگام کامپایل تنها پیغام هشداری تولید خواهد شد. در پیغام این برنامه، عنصری از برنامه را که نباید از آن استفاده شود معین شده و جایگزین آن نیز معرفی می‌شود.

 

AnyClass.Old()' is obsolete: 'Don't use Old method,  use New method'

 نکته مهمی که باید در مورد صفتها در نظر بگیرید آنست که اطلاعاتی که توسط صفت در کد برنامه قرار می‌گیرد، توسط سایر برنامه‌ها نیز قابل تفسیر و استفاده است.

 انواع پارامترهای صفت (پارامترهای Positional و Named)

همانطور که در بالا نیز اشاره شد، صفتها می‌توانند دارای پارامتر نیز باشند. این پارامترها به دو دسته تقسیم می‌شوند. پارامترهای محلی (positional) و پارامترهای اسمی (named). از پارامترهای positional در زمانی استفاده می‌شود که می‌خواهیم پارامتر مورد نظر بصورت اجباری مورد استفاده قرار گیرد و البته این مسئله یک قانون نیست ! چراکه در مورد صفت Obsolete، این صفت دارای یک پارامتر positional دیگر با نام error و از نوع int نیز می‌باشد که ما آنرا در مثال 1-16 لحاظ نکردیم. همانطور که در مثال 2-16 مشاهده کردید، از این پارامتر positional می‌توان برای ایجاد یک خطا در زمان کامپایل برنامه استفاده نمود.

 
[Obsolete("Don't use Old method, use New method", true)]
static void Old( ) { }

 

تفاوت پارامترهای positional با پارامترهای named در آنست که، پارامترهای named با نامشان مورد استفاده قرار می‌گیرند و همیشه اختیاری هستند. در مثال 3-16 صفت DllImport را مشاهده می‌نمایید که دارای هر دو نوع پارامتر positional و named است.

مثال 3-16

 

using System;
using System.Runtime.InteropServices;

class AttributeParamsDemo
{
    [DllImport("User32.dll", EntryPoint="MessageBox")]
    static extern int MessageDialog(int hWnd, string msg, string caption, int msgType);

    [STAThread]
    static void Main(string[] args)
    {
        MessageDialog(0, "MessageDialog Called!", "DllImport Demo", 0);
    }
}

صفت DllImport در مثال 3-16 دارای یک پارامتر positional ("User32.dll") و یک پارامتر named (EntryPoint="MessageBox") است . پارامترهای named در هر مکانی می‌توانند قرار گیرند و مانند پارامترهای positional دارای محدودیت مکانی نیستند. بدین معنا که چون در پارامترهای named، نام پارامتر مستقیما مورد استفاده قرار می‌گیرد، محل قرار گیری آن در لیست پارامترهای صفت مهم نیست اما در مورد پارامترهای positional چون اسم پارامتر مورد استفاده قرار نمی‌گیرد، این پارامترها حتما باید در مکانهای تعیین شده و تعریف شده در لیست پارامترهای صفت قرار گیرند. توجه کنید که چون هدف ما تنها آشنایی با صفتها و نحوه استفاده از آنهاست، درباره پارامترهای مختلف صفت DllImport بحث نخواهیم کرد چراکه پارامترهای این صفت نیاز به آشنایی کامل با Win32 API دارد.

 در یک بررسی کلی می‌توان گفت که پارامترهای Positional، پارامترهای سازنده(Constructor)  صفت هستند و در هر بار استفاده از صفت باید مورد استفاده قرار گیرند، ولی پارامترهای Named کاملا اختیاری هستند و همیشه نیازی به استفاده از آنها نمی‌باشد.

 Target های صفتها (عناصری که صفتها بر روی آنها اعمال می‌شوند)

صفتهایی که تا کنون مشاهده کردید، همگی بر روی متدها اعمال شده بودند. اما عناصر مختلف دیگری در C# وجود دارند که می‌توان صفتها را بر روی آنها اعمال نمود. جدول 1-16 عناصر مختلف زبان C# را که صفتها بر روی آنها اعمال می‌شوند را نشان می‌دهد.

 

قابل اعمال به ....

عناصر اعمال شونده

به تمامی عناصر قابل اعمال هستند.

all

به تمام یک اسمبلی

assembly

کلاسها

class

سازنده‌ها

constructor

Delegate ها

delegates

عناصر شمارشی

enum

رخدادها

event

فیلدها

field

واسطها

interface

متدها

method

ماژولها (کدهای کامپایل شده‌ای که می‌توانند به عنوان قسمتی از یک اسمبلی در نظر گرفته شوند.)

module

پارامترها

parameter

Property ها

property

مقادیر بازگشتی

returnvalue

ساختارها

struc

 هر چند ممکن است استفاده از این Target ها باعث ایجاد ابهام شوند، اما می‌توان با استفاده از این Target ها معین کرد که صفت دقیقا به عنصر مورد نظر اعمال شود. یکی از صفتهایی که بر روی اسمبلی اعمال می‌شود و باعث ارتباط با CLS می‌گردد، صفت CLSCompliantAttribute است. CLS یا همان Common Language Specification امکان برقراری ارتباط بین کلیه زبانهایی که تحت .Net کار می‌کنند را فراهم می‌نماید. Target های صفتها با استفاده از اسم Target که بعد از آن کولون قرار می‌گیرد، ایجاد می‌شوند. در مثال 4-16 نحوه استفاده از این صفت نشان داده شده است.

 مثال 4-16

using System;

[assembly:CLSCompliant(true)]

public class AttributeTargetDemo
{
    public void NonClsCompliantMethod(uint nclsParam)
    {
        Console.WriteLine("Called NonClsCompliantMethod().");
    }

    [STAThread]
    static void Main(string[] args)
    {
        uint myUint = 0;

        AttributeTargetDemo tgtDemo = new AttributeTargetDemo();

        tgtDemo.NonClsCompliantMethod(myUint);
    }
}

با استفاده از Target مورد نظر در اینجا یعنی assembly، این صفت بر روی کل اسمبلی اعمال می‌گردد. کد موجود در مثال 4-16 کامپایل نخواهد شد، زیرا uint در متد NonClsCompliantMethod() اعلان شده است. در اینجا درصورتیکه فرم پارامتر صفت CLSCompliant را به false تغییر دهید و یا متد NonClsCompliantMethod() را به متدی منطبق با CLS تبدیل کنید (مثلا نوع بازگشتی آنرا int تعریف کنید) آنگاه برنامه کامپایل خواهد شد. (توضیحی که درباره CLS میتوانم بیان کنم اینست که CLS مجموعه‌ای از ویژگیها و خواص .Net Framework است که به نحوی بیان می‌دارد، برای اینکه زبانهای مختلف تحت .Net بتوانند بدون مشکل با یکدیگر ارتباط برقرار نمایند، لازم است از یک سری از قوانین پیروی کنند، در غیر اینصورت امکان برقراری ارتباط با سایر کدهای نوسته شده تحت زبانهای برنامه‌سازی دیگر را نخواهند داشت. برای مثال، استفاده از نوع uint به دلیل اینکه در زبانهای مختلف می‌تواند به صورتهای متفاوتی پیاده‌سازی شود و یا وجود نداشته باشد، سازگار با CLS نیست و برای اینکه بخواهیم برنامه‌ای منطبق با CLS داشته باشیم نباید از آن استفاده نماییم.)

 نکته قابل توجه در مورد مثال 4-16 آنست که در این مثال صفت CLSCompliant به استفاده از یک Target که همان assembly است، مورد استفاده قرار گرفته است و از اینرو تمامی مشخصات این صفت به کلیه اعضای این اسمبلی اعمال خواهند شد. توجه نمایید که در این مثال علت و موارد استفاده از صفتها مشهودتر است، چراکه همانطور که مشاهده می‌نمایید، با استفاده از یک صفت می‌توانیم کنترلی بر روی کل اسمبلی و برنامه قرار دهیم تا در صورتیکه می‌‌خواهیم برنامه ما با سایر زبانهای برنامه‌سازی تحت .Net ارتباط برقرار کند، از متدهای استاندارد و سازگار با CLS استفاده نماییم که این قابلیت بزرگی را در اختیار ما قرار خواهد داد.

 تولید صفتهای شخصی

پس از اینکه با طریقه استفاده از صفتهای موجود در زبان آشنا شدید، حال نوبت به ساخت صفتهای شخصی می‌رسد. برای تولید یک صفت (Attribute) باید یک کلاس ایجاد نماییم و این کلاس باید از System.Attribute مشتق شود. کلاسی که از System.Attribute مشتق می‌شود (چه بطور مستقیم و چه بطور غیر مستقیم) یک کلاس صفت(Attribute Class)  است. اعلان کلاس صفت باعث ایجاد صفت جدیدی می‌شود که می‌توان از آن در برنامه استفاده نمود. به مثال 5-16 توجه فرمایید.

 مثال 5-16

using System;
public class HelpAttribute : Attribute
{
}

در این مثال به سادگی یک صفت جدید تولید کرده‌ایم و می‌توانیم از آن استفاده کنیم.

 

[Help()]
public class AnyClass
{
}

 همانطور که قبلا نیز گفتیم استفاده از کلمه Attribute به دنبال نام صفت الزامی نیست. صفتی که در اینجا ایجاد کرده‌ایم عملا کار خاصی برای ما انجام نمی‌دهد پس اندکی در کد آن تغییر ایجاد می‌کنیم تا مفیدتر باشد.

 مثال 6-16

 

using System;
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Descrition_in)
    {
        this.description = Description_in;
    }
    protected String description;
    public String Description 
    {
        get 
        {
            return this.description;
                 
        }            
    }    
}
[Help("this is a do-nothing class")]
public class AnyClass
{
}

 هماطور که مشاهده می‌کنید با اضافه کردن چند خط کد توانستیم این صفت را کاراتر کنیم. با قرار دادن یک property در این صفت، پارامتر این صفت بعنوان پیغام نمایش داده می‌شود.