‏نمایش پست‌ها با برچسب C#. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب C#. نمایش همه پست‌ها

۱۳۹۰/۱۲/۱۲

تعريف نوع جنريك به صورت متغير


در تهيه مثال Auto Mapping به كمك امكانات توكار NH 3.2 به اين مورد نياز پيدا كردم:
بتوان نوع متد جنريك را به صورت متغير تعريف كرد و اين نوع در زمان كامپايل برنامه مشخص نباشد. مثلا چيزي شبيه به اين مثال:

using System;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var type = typeof(Nullable<int>);
            TestGenerics.Print<type>(1);
        }
    }
}

اين نوع فراخواني متد Print در دات نت به صورت پيش فرض غيرمجاز است و نوع جنريك را نمي‌توان به صورت متغير معرفي كرد.
كه البته اين هم راه حل دارد و به كمك Reflection قابل حل است:

using System;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
            var method = typeof(TestGenerics).GetMethod("Print");
            var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
            genericMethod.Invoke(null, new object[] { 1 });
        }
    }
}

دو متد MakeGenericType و MakeGenericMethod براي ساخت پوياي نوع‌هاي جنريك و همچنين ارسال آن‌ها به متدهاي جنريك در دات نت وجود دارند كه مثالي از نحوه استفاده از آن‌ها را در بالا ملاحظه مي‌كنيد.

مثال دوم:
اگر كلاس TestGenerics نسخه غيرجنريك متد Print را هم داشت، ‌چطور؟ مثلا:

class TestGenerics
{
    public static void Print<T>(T data)
    {
         Console.WriteLine("Print<T>");
    }

    public static void Print(object data)
    {
         Console.WriteLine("Print");
    }
}

اينبار اگر برنامه فوق را اجرا كنيم، پيغام Ambiguous match found را حين فراخواني GetMoethod دريافت خواهيم كرد؛ چون دو متد با يك نام در كلاس ياد شده وجود دارند. براي حل اين مشكل بايد به نحو زير عمل كرد:

using System;
using System.Linq;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }

        public static void Print(object data)
        {
            Console.WriteLine("Print");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
            var method = typeof(TestGenerics).GetMethods()
                         .First(x => x.Name == "Print" && (x.GetParameters()[0]).ParameterType.IsGenericParameter);
            var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
            genericMethod.Invoke(null, new object[] { 1 });
        }
    }
}

GetMethods تمام متدها را بازگشت داده و سپس بر اساس متاديتاي متدها، ‌مي‌توان تشخيص داد كه كدام يك جنريك است.

۱۳۹۰/۰۶/۲۸

تبديل عدد به حروف


به طور قطع توابع و كلاس‌هاي تبديل عدد به حروف، در جعبه ابزار توابع كمكي شما هم پيدا مي‌شوند. روز قبل سعي كردم جهت آزمايش، عدد 3000,000,000,000,000 ريال را با كلاسي كه دارم تست كنم و نتيجه overflow يا اصطلاحا تركيدن سيستم بود! البته اگر مطالب اين سايت را دنبال كرده باشيد پيشتر در همين راستا مطلبي در مورد نحوه‌ي صحيح بكارگيري توابع تجمعي SQL در اين سايت منتشر شده است و جزو الزامات هر سيستمي است (تفاوتي هم نمي‌كند كه به چه زباني تهيه شده باشد). اگر آ‌ن‌را رعايت نكرده‌ايد، سيستم شما «روزي» دچار overflow خواهد شد.

در كل اين كلاس تبديل عدد به حروف را به صورت ذيل اصلاح كردم و همچنين دو زبانه است؛ چيزي كه كمتر در پياده سازي‌هاي عمومي به آن توجه شده است:

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

namespace NumberToWordsLib
{
 /// <summary>
 /// Number to word languages
 /// </summary>
 public enum Language
 {
  /// <summary>
  /// English Language
  /// </summary>
  English,

  /// <summary>
  /// Persian Language
  /// </summary>
  Persian
 }

 /// <summary>
 /// Digit's groups
 /// </summary>
 public enum DigitGroup
 {
  /// <summary>
  /// Ones group
  /// </summary>
  Ones,

  /// <summary>
  /// Teens group
  /// </summary>
  Teens,

  /// <summary>
  /// Tens group
  /// </summary>
  Tens,

  /// <summary>
  /// Hundreds group
  /// </summary>
  Hundreds,

  /// <summary>
  /// Thousands group
  /// </summary>
  Thousands
 }

 /// <summary>
 /// Equivalent names of a group 
 /// </summary>
 public class NumberWord
 {
  /// <summary>
  /// Digit's group
  /// </summary>
  public DigitGroup Group { set; get; }

  /// <summary>
  /// Number to word language
  /// </summary>
  public Language Language { set; get; }

  /// <summary>
  /// Equivalent names
  /// </summary>
  public IList<string> Names { set; get; }
 }

 /// <summary>
 /// Convert a number into words
 /// </summary>
 public static class HumanReadableInteger
 {
  #region Fields (4)

  private static readonly IDictionary<Language, string> And = new Dictionary<Language, string>
  {
   { Language.English, " " },
   { Language.Persian, " و " } 
  };
  private static readonly IList<NumberWord> NumberWords = new List<NumberWord>
  {
   new NumberWord { Group= DigitGroup.Ones, Language= Language.English, Names=
    new List<string> { string.Empty, "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }},
   new NumberWord { Group= DigitGroup.Ones, Language= Language.Persian, Names=
    new List<string> { string.Empty, "يك", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه" }},

   new NumberWord { Group= DigitGroup.Teens, Language= Language.English, Names=
    new List<string> { "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen" }},
   new NumberWord { Group= DigitGroup.Teens, Language= Language.Persian, Names=
    new List<string> { "ده", "يازده", "دوازده", "سيزده", "چهارده", "پانزده", "شانزده", "هفده", "هجده", "نوزده" }},

   new NumberWord { Group= DigitGroup.Tens, Language= Language.English, Names=
    new List<string> { "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety" }},
   new NumberWord { Group= DigitGroup.Tens, Language= Language.Persian, Names=
    new List<string> { "بيست", "سي", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" }},

   new NumberWord { Group= DigitGroup.Hundreds, Language= Language.English, Names=
    new List<string> {string.Empty, "One Hundred", "Two Hundred", "Three Hundred", "Four Hundred", 
     "Five Hundred", "Six Hundred", "Seven Hundred", "Eight Hundred", "Nine Hundred" }},
   new NumberWord { Group= DigitGroup.Hundreds, Language= Language.Persian, Names=
    new List<string> {string.Empty, "يكصد", "دويست", "سيصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد" , "نهصد" }},

   new NumberWord { Group= DigitGroup.Thousands, Language= Language.English, Names=
     new List<string> { string.Empty, " Thousand", " Million", " Billion"," Trillion", " Quadrillion", " Quintillion", " Sextillian",
   " Septillion", " Octillion", " Nonillion", " Decillion", " Undecillion", " Duodecillion", " Tredecillion",
   " Quattuordecillion", " Quindecillion", " Sexdecillion", " Septendecillion", " Octodecillion", " Novemdecillion",
   " Vigintillion", " Unvigintillion", " Duovigintillion", " 10^72", " 10^75", " 10^78", " 10^81", " 10^84", " 10^87",
   " Vigintinonillion", " 10^93", " 10^96", " Duotrigintillion", " Trestrigintillion" }},
   new NumberWord { Group= DigitGroup.Thousands, Language= Language.Persian, Names=
     new List<string> { string.Empty, " هزار", " ميليون", " ميليارد"," تريليون", " Quadrillion", " Quintillion", " Sextillian",
   " Septillion", " Octillion", " Nonillion", " Decillion", " Undecillion", " Duodecillion", " Tredecillion",
   " Quattuordecillion", " Quindecillion", " Sexdecillion", " Septendecillion", " Octodecillion", " Novemdecillion",
   " Vigintillion", " Unvigintillion", " Duovigintillion", " 10^72", " 10^75", " 10^78", " 10^81", " 10^84", " 10^87",
   " Vigintinonillion", " 10^93", " 10^96", " Duotrigintillion", " Trestrigintillion" }},
  };
  private static readonly IDictionary<Language, string> Negative = new Dictionary<Language, string>
  {
   { Language.English, "Negative " },
   { Language.Persian, "منهاي " } 
  };
  private static readonly IDictionary<Language, string> Zero = new Dictionary<Language, string>
  {
   { Language.English, "Zero" },
   { Language.Persian, "صفر" } 
  };

  #endregion Fields

  #region Methods (7)

  // Public Methods (5) 

  /// <summary>
  /// display a numeric value using the equivalent text
  /// </summary>
  /// <param name="number">input number</param>
  /// <param name="language">local language</param>
  /// <returns>the equivalent text</returns>
  public static string NumberToText(this int number, Language language)
  {
   return NumberToText((long)number, language);
  }


  /// <summary>
  /// display a numeric value using the equivalent text
  /// </summary>
  /// <param name="number">input number</param>
  /// <param name="language">local language</param>
  /// <returns>the equivalent text</returns>
  public static string NumberToText(this uint number, Language language)
  {
   return NumberToText((long)number, language);
  }

  /// <summary>
  /// display a numeric value using the equivalent text
  /// </summary>
  /// <param name="number">input number</param>
  /// <param name="language">local language</param>
  /// <returns>the equivalent text</returns>
  public static string NumberToText(this byte number, Language language)
  {
   return NumberToText((long)number, language);
  }

  /// <summary>
  /// display a numeric value using the equivalent text
  /// </summary>
  /// <param name="number">input number</param>
  /// <param name="language">local language</param>
  /// <returns>the equivalent text</returns>
  public static string NumberToText(this decimal number, Language language)
  {
   return NumberToText((long)number, language);
  }

  /// <summary>
  /// display a numeric value using the equivalent text
  /// </summary>
  /// <param name="number">input number</param>
  /// <param name="language">local language</param>
  /// <returns>the equivalent text</returns>
  public static string NumberToText(this double number, Language language)
  {
   return NumberToText((long)number, language);
  }

  /// <summary>
  /// display a numeric value using the equivalent text
  /// </summary>
  /// <param name="number">input number</param>
  /// <param name="language">local language</param>
  /// <returns>the equivalent text</returns>
  public static string NumberToText(this long number, Language language)
  {
   if (number == 0)
   {
    return Zero[language];
   }

   if (number < 0)
   {
    return Negative[language] + NumberToText(-number, language);
   }

   return wordify(number, language, string.Empty, 0);
  }
  // Private Methods (2) 

  private static string getName(int idx, Language language, DigitGroup group)
  {
   return NumberWords.Where(x => x.Group == group && x.Language == language).First().Names[idx];
  }

  private static string wordify(long number, Language language, string leftDigitsText, int thousands)
  {
   if (number == 0)
   {
    return leftDigitsText;
   }

   var wordValue = leftDigitsText;
   if (wordValue.Length > 0)
   {
    wordValue += And[language];
   }

   if (number < 10)
   {
    wordValue += getName((int)number, language, DigitGroup.Ones);
   }
   else if (number < 20)
   {
    wordValue += getName((int)(number - 10), language, DigitGroup.Teens);
   }
   else if (number < 100)
   {
    wordValue += wordify(number % 10, language, getName((int)(number / 10 - 2), language, DigitGroup.Tens), 0);
   }
   else if (number < 1000)
   {
    wordValue += wordify(number % 100, language, getName((int)(number / 100), language, DigitGroup.Hundreds), 0);
   }
   else
   {
    wordValue += wordify(number % 1000, language, wordify(number / 1000, language, string.Empty, thousands + 1), 0);
   }

   if (number % 1000 == 0) return wordValue;
   return wordValue + getName(thousands, language, DigitGroup.Thousands);
  }

  #endregion Methods
 }
}



دريافت پروژه كامل به همراه Unit tests مرتبط


۱۳۹۰/۰۵/۲۹

ايجاد ليستي از كلاسي جنريك

كلاس جنريك زير را در نظر بگيريد:
public class Column<T>
{
     public string Name { set; get; }
     public T Data { set; get; }
}

مشكلي كه با اين نوع كلاس‌ها وجود دارد اين است كه نمي‌توان مثلا ليست زير را در مورد آن‌ها تعريف كرد:

IList<Column<T>> myList = new List<Column<T>>();


به عبارتي مي‌خواهيم يك ليست از كلاسي جنريك داشته باشيم. راه حل انجام آن به صورت زير است:

using System.Collections;

namespace Tests
{
    public interface IColumn
    {
        string Name { set; get; }
        object Data { set; get; }
    }

    public class Column<T> : IColumn
    {
        public string Name { set; get; }

        public T Data { set; get; }

        object IColumn.Data
        {
            get { return this.Data; }
            set {  this.Data = (T)value; }
        }
    }
}

ابتدا يك اينترفيس عمومي را همانند اعضاي كلاس Column تعريف مي‌كنيم كه در آن بجاي T از object‌ استفاده شده است. سپس يك پياده سازي جنريك از اين اينترفيس را ارائه خواهيم داد؛ با اين تفاوت كه اينبار خاصيت Data مربوط به اينترفيس، به صورت خصوصي و صريح با استفاده از IColumn.Data تعريف مي‌شود و نمونه‌ي جنريك هم نام آن، عمومي خواهد بود.
اكنون مي‌توان نوشت:

var myList = new List<IColumn>();

براي مثال در اين حالت تعريف ليست زير كه از تعدادي وهله‌ي كلاسي جنريك ايجاد شده، كاملا مجاز مي‌باشد:

var myList = new List<IColumn>
{
    new Column<int> { Data = 1, Name = "Col1"},
    new Column<double> { Data = 1.2, Name = "Col2"}
};

خوب، تا اينجا يك مرحله پيشرفت است.اكنون اگر بخواهيم در اين ليست، Data مثلا عنصري را كه نامش Col1 است، دريافت كنيم چه بايد كرد؟ آن هم نه به شكل object بلكه از نوع T مشخص:

static T GetColumnData<T>(IList<IColumn> list, string name)
{
    var column = (Column<T>)Convert.ChangeType(list.Single(s => s.Name.Equals(name)), typeof(Column<T>), null);
    return column.Data;
}

و نمونه‌اي از استفاده آن:

int data = GetColumnData<int>(myList, "Col1");

۱۳۹۰/۰۵/۲۰

كاربردهاي Static reflection - قسمت اول


در مورد static reflection مقدمه‌اي پيشتر در اين سايت قابل مطالعه است (^) و پيشنياز بحث جاري است. در ادامه قصد داريم يك سري از كاربردهاي متداول آن‌را كه اين روزها در گوشه و كنار وب يافت مي‌شود، به زبان ساده بررسي كنيم.

بهبود كدهاي موجود

از static reflection در دو حالت كلي مي‌توان استفاده كرد. يا قرار است كتابخانه‌اي را از صفر طراحي كنيم يا اينكه خير؛ كتابخانه‌اي موجود است و مي‌خواهيم كيفيت آن‌را بهبود ببخشيم. هدف اصلي هم «حذف رشته‌ها» و «استفاده از كد بجاي رشته‌ها» است.
براي مثال قطعه كد زير يك مثال متداول مرتبط با WPF و يا Silverlight است. در آن با پياده سازي اينترفيس INotifyPropertyChanged و استفاده از متد raisePropertyChanged ، به رابط كاربري برنامه اعلام خواهيم كرد كه لطفا خودت را بر اساس اطلاعات جديد تنظيم شده در قسمت set خاصيت Name ، به روز كن:
using System.ComponentModel;

namespace StaticReflection
{
    public class User : INotifyPropertyChanged
    {
        string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                raisePropertyChanged("Name");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        void raisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler == null) return;
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

تعاريف قسمت PropertyChangedEventArgs اين پياده سازي، خارج از كنترل ما است و در دات نت فريم ورك تعريف شده است. حتما هم نياز به رشته دارد؛ آن هم نام خاصيتي كه تغيير كرده است. چقدر خوب مي‌شد اگر مي‌توانستيم اين رشته را حذف كنيم تا كامپايلر بتواند صحت بكارگيري اطلاعات وارد شده را دقيقا پيش از اجراي برنامه بررسي كند. الان فقط در زمان اجرا است كه متوجه خواهيم شد، مثلا آيا به روز رساني مورد نظر صورت گرفته‌است يا خير؛ اگر نه، يعني احتمالا يك اشتباه تايپي جايي وجود دارد.
براي بهبود اين كد همانطور كه در قسمت قبل نيز گفته شد، از تركيب كلاس‌هاي Expression و Func استفاده خواهيم كرد. در اينجا Func قرار نيست چيزي را اجرا كند، بلكه از آن به عنوان قطعه‌ كدي كه اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده مي‌شود. اين استخراج اطلاعات هم توسط كلاس Expression انجام مي‌شود. بنابراين قسمت اول بهبود كد به صورت زير شروع مي‌شود:
void raisePropertyChanged(Expression<Func<object>> expression)

الان اگر متد raisePropertyChanged بكارگرفته شده در خاصيت Name را بخواهيم اصلاح كنيم، حداقل با دو واقعه‌ي مطلوب زير مواجه خواهيم شد:
Intellisense به صورت خودكار كار مي‌كند:


حتي بدوي‌ترين ابزارهاي Refactoring موجود (منظور همان ابزار توكار VS.NET است!) هم امكان Refactoring را در اينجا فراهم خواهند ساخت:



در پايان كد تكميل شده فوق به شرح زير خواهد بود كه در آن از كلاس Expression جهت استخراج Member.Name استفاده شده است:
using System;
using System.ComponentModel;
using System.Linq.Expressions;

namespace StaticReflection
{
    public class User : INotifyPropertyChanged
    {
        string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                raisePropertyChanged(() => Name);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        void raisePropertyChanged(Expression<Func<object>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;
            if (memberExpression == null)
                throw new InvalidOperationException("Not a member access.");

            var handler = PropertyChanged;
            if (handler == null) return;
            handler(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
        }
    }
}

در اينجا باز هم نهايتا به همان PropertyChangedEventArgs استاندارد و موجود، برمي‌گرديم؛ اما آرگومان رشته‌اي آن‌را به كمك تركيب كلاس‌هاي Expression و Func تامين خواهيم كرد.

۱۳۹۰/۰۵/۰۹

Static Reflection


قابليت Dynamic reflection يا به اختصار همان reflection متداول، از اولين نگارش‌هاي دات نت فريم در دسترس است و امكان دسترسي به اطلاعات مرتبط با كلاس‌ها، متدها، خواص و غيره را در زمان اجرا مهيا مي‌سازد. تابحال به كمك اين قابليت، امكان تهيه‌ي ابزارهاي پيشرفته‌ي زير مهيا شده است:
انواع و اقسام
- فريم ورك‌هاي آزمون واحد
- code generators
- ORMs
- ابزارهاي آناليز كد
و ...


براي مثال فرض كنيد كه مي‌خواهيد براي يك كلاس به صورت خودكار، متدهاي آزمون واحد تهيه كنيد (تهيه يك code generator ساده). اولين نياز اين برنامه، دسترسي به امضاي متدها به همراه نام آرگومان‌ها و نوع آن‌ها است. براي حل اين مساله بايد براي مثال يك parser زبان سي شارپ يا اگر بخواهيد كامل‌تر كار كنيد، به ازاي تمام زبان‌هاي قابل استفاده در دات نت فريم ورك بايد parser تهيه كنيد كه ... كار ساده‌اي نيست. اما با وجود reflection به سادگي مي‌توان به اين نوع اطلاعات دسترسي پيدا كرد و نكته‌ي مهم آن هم اين است كه مستقل است از نوع زبان مورد استفاده. به همين جهت است كه اين نوع ابزارها را در فريم ورك‌هايي كه فاقد امكانات reflection هستند، كمتر مي‌توان يافت. براي مثال كيفيت كتابخانه‌هاي آزمون واحد CPP در مقايسه با آنچه كه در دات نت مهيا هستند، اصلا قابل مقايسه نيستند. براي نمونه به يكي از معظم‌ترين فريم ورك‌هاي آزمون واحد CPP كه توسط گوگل تهيه شده مراجعه كنيد : (+)
قابليت Reflection ، مطلب جديدي نيست و براي مثال زبان جاوا هم سال‌ها است كه از آن‌ پشتيباني مي‌كند. اما نگارش سوم دات نت فريم ورك با معرفي lambda expressions ، LINQ و Expressions در يك سطح بالاتر از اين Dynamic reflection متداول قرار گرفت.

تعريف Static Reflection :
استفاده از امكانات Reflection API بدون بكارگيري رشته‌ها، به كمك قابليت اجراي به تعويق افتاده‌ي LINQ، جهت دسترسي به متاديتاي المان‌هاي كد، مانند خواص، متدها و غيره.
براي مثال كد زير را در نظر بگيريد:
//dynamic reflection
PropertyInfo property = typeof (MyClass).GetProperty("Name");
MethodInfo method = typeof (MyClass).GetMethod("SomeMethod");
اين كد، يك نمونه از دسترسي به متاديتاي خواص يا متدها را به كمك Reflection متداول نمايش مي‌دهد. مهم‌ترين ايراد آن استفاده از رشته‌ها است كه تحت نظر كامپايلر نيستند و تنها زمان اجرا است كه مشخص مي‌شود آيا MyClass واقعا خاصيتي به نام Name داشته است يا خير.
چقدر خوب مي‌شد اگر اين قابليت بجاي dynamic بودن (مشخص شدن در زمان اجرا)، استاتيك مي‌بود و در زمان كامپايل قابل بررسي مي‌شد. اين امكان به كمك lambda expressions و expression trees دات نت سه بعد، ميسر شده است. كليدهاي اصلي Static Reflection كلاس‌هاي Func و Expression هستند. با استفاده از كلاس Func مي‌توان lambda expression ايي را تعريف كرد كه مقداري را بر مي‌گرداند و توسط كلاس Expression مي‌توان به محتواي يك delegate دسترسي يافت. تركيب اين دو، قدرت دستيابي به اطلاعاتي مانند PropertyInfo را در زمان طراحي كلاس‌ها، مي‌دهد؛ با توجه به اينكه:
- كاملا توسط intellisense موجود در VS.NET پشتيباني مي‌شود.
- با استفاده از ابزارهاي refactoring قابل كنترل است.
- از همه مهم‌تر، ديگري خبري از رشته‌ها نبوده و همه چيز تحت كنترل كامپايلر قرار مي‌گيرد.

و شايد هيچ قابليتي به اندازه‌ي Static Reflection در اين چندسال اخير بر روي اكوسيستم دات نت فريم ورك تاثيرگذار نبوده باشد. اين روزها كمتر كتابخانه يا فريم وركي را مي‌توانيد پيدا كنيد كه از Static Reflection استفاده نكند. سرآغاز استفاده گسترده از آن به Fluent NHibernate بر مي‌گردد؛ سپس در انواع و اقسام mocking frameworks‌ ، ORMs و غيره استفاده شد و مدتي است كه در ASP.NET MVC نيز مورد استفاده قرار مي‌گيرد (براي مثال TextBoxFor معروف آن):
public string TextBoxFor<T>(Expression<Func<T,object>> expression);
به اين ترتيب حين استفاده از آن ديگري نيازي نخواهد بود تا نام خاصيت مدل مورد نظر را به صورت رشته وارد كرد:
<%= this.TextBoxFor(model => model.FirstName); %>

يك مثال ساده از تعريف و بكارگيري Static Reflection :
public PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;

if (memberExpression == null)
throw new InvalidOperationException("Not a member access.");

return memberExpression.Member as PropertyInfo;
}
همانطور كه عنوان شد كليدهاي اصلي بهر‌ه‌گيري از امكانات Static reflection ، استفاده از كلاس‌هاي Expression و Func هستند كه در آرگومان متد فوق بكارگرفته شده‌اند و در حقيقت يك expression of a delegate است كه به آن Lambdas as Data نيز گفته مي‌شود. اين delegate پارامتري از نوع T را دريافت كرده و سپس مقداري از نوع object را بر مي‌گرداند. اما زمانيكه از كلاس Expression در اينجا استفاده مي‌شود، اين Func ديگر اجرا نخواهد شد، بلكه از آن به عنوان قطعه‌ كدي كه اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده مي‌شود.
براي نمونه Fluent NHibernate‌ در پشت صحنه متد Map ، به كمك متدي شبيه به GetProperty فوق، a => a.Address1 را به رشته متناظر خاصيت Address1 تبديل كرده و جهت تعريف نگاشت‌ها مورد استفاده قرار مي‌دهد:
public class AddressMap : DomainMap<Address>
{
public AddressMap()
{
Map(a => a.Address1);
}
}

جهت اطلاع؛ قابليت استفاده از «كد به عنوان اطلاعات» هم مفهوم جديدي نيست و براي مثال زبان Lisp چند دهه است كه آن‌را ارائه داده است!

براي مطالعه بيشتر:

۱۳۹۰/۰۳/۲۲

سرعت ايجاد اشياء CLR


به نظر شما چه تعداد شيء CLR را مي‌توان در يك ثانيه ايجاد كرد؟
برنامه كنسول زير دو نسخه معمولي و نسخه پردازش موازي يك آزمايش ساده را براي اندازه گيري اين مطلب ارائه مي‌دهد:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ObjectInitSpeedTest
{
class Program
{
//Note: don't forget to build it in Release mode.
static void Main()
{
normalSpeedTest();
parallelSpeedTest();

Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Press a key ...");
Console.ReadKey();
}

private static void parallelSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("parallelSpeedTest");

long totalObjectsCreated = 0;
long totalElapsedTime = 0;

var tasks = new List<Task>();
var processorCount = Environment.ProcessorCount;

Console.WriteLine("Running on {0} cores", processorCount);

for (var t = 0; t < processorCount; t++)
{
tasks.Add(Task.Factory.StartNew(
() =>
{
const int reps = 1000000000;
var sp = Stopwatch.StartNew();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Interlocked.Add(ref totalObjectsCreated, reps);
Interlocked.Add(ref totalElapsedTime, sp.ElapsedMilliseconds);
}
));
}

// let's complete all the tasks
Task.WaitAll(tasks.ToArray());

Console.WriteLine("Created {0:N} objects in 1 sec\n", (totalObjectsCreated / (totalElapsedTime / processorCount)) * 1000);
}

private static void normalSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("normalSpeedTest");

const int reps = 1000000000;
var sp = Stopwatch.StartNew();
sp.Start();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Console.WriteLine("Created {0:N} objects in 1 sec\n", (reps / sp.ElapsedMilliseconds) * 1000);
}
}
}

هنگام اجراي برنامه فراموش نكنيد كه حالت Build را بر روي Release قرار دهيد.



۱۳۹۰/۰۳/۱۳

فلسفه وجودي Path.Combine


عموما اكثر كدهاي موجود از روش زير براي ساخت يك مسير استفاده مي‌كنند:
string path = somePath + "\\" + filename;

اما اگر همين برنامه تحت Mono در لينوكس اجرا شود به مشكل بر مي‌خورد زيرا در لينوكس مسيرها اين‌بار به صورت زير هستند:
/somepath/filename

به همين جهت توصيه شده است براي ساخت مسيرها در برنامه‌ي خود، از متد Path.Combine موجود در فضاي نام System.IO استفاده كنيد زيرا اين متد از مقادير Path.DirectorySeperatorChar و Path.VolumeSeparatorChar جهت تهيه مسير نهايي استفاده مي‌كند. اين مقادير در ويندوز (\) و لينوكس (/) متفاوت بوده و به صورت خودكار در زمان اجرا توسط فريم ورك مورد استفاده مديريت خواهند شد.
همچنين مزيت ديگر استفاده از Path.Combine ، تعيين اعتبار ورودي است؛ به اين معنا كه اگر از كاراكترهاي غيرمجاز استفاده شود، يك استثناء صادر خواهد شد.

يك مورد ديگر هم شايد بد نباشد همينجا اضافه شود و آن هم فلسفه وجودي Environment.NewLine است. مطابق معمول رسم بر اين است كه سطر جديد با n\ در انتهاي يك رشته مشخص شود اما اين هميشه صحيح نيست و در پلتفرم‌هاي مختلف متفاوت است. Environment.NewLine در ويندوز مساوي r\n\ است و در سيستم‌هاي مبتني بر Unix مساوي n\ خواهد بود. به همين جهت بهتر است از اين پس بجاي n\ از Environment.NewLine جهت مشخص سازي سطر جديد استفاده كنيد.

۱۳۸۹/۰۸/۳۰

تغييرات دسترسي به كدها در دات نت 4


دو پروژه‌ي سورس باز XML RPC و Log4Net براي اجرا شدن در برنامه‌هاي دات نت 4 نياز به اندكي تغيير در هر دو برنامه‌ي فراخوان و اسمبلي‌هاي آن‌ها دارند كه در ادامه توضيحات مربوطه ارائه خواهند شد.

اگر يك پروژه‌ي جديد دات نت 4 را آغاز كنيد و سپس ارجاعي را به يكي از اسمبلي‌هاي ذكر شده اضافه نمائيد، اولين خطايي را كه حين استفاده مشاهده خواهيد نمود، مورد زير است:
Could not resolve assembly "System.Web".
The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client".
Please remove references to assemblies not in the targeted framework or consider retargeting your project.
علت هم اينجا است كه در تنظيمات پروژه‌ها‌ي جديد مبتني بر دات نت 4، پيش فرض Target framework انتخابي توسط VS.NET 2010 از نوع Client profile است؛ كه صرفا جهت كاهش حجم دات نت فريم ورك مورد نياز اين نوع برنامه‌ها طراحي شده است. در اين پروفايل ساده شده، اسمبلي System.Web وجود ندارد. بنابراين جهت استفاده از كتابخانه‌هاي XML RPC و يا Log4Net نياز است تا در خواص پروژه، Target framework را بر روي دات نت فريم ورك 4 كامل قرار داد تا خطاي فوق برطرف شود.

خطاي دومي كه حين كار با كتابخانه‌هاي XML RPC و يا Log4Net در يك برنامه‌ي دات نت 4 حتما با آن مواجه خواهيد شد در ادامه ذكر گرديده است:
Inheritance security rules violated while overriding member:
GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext),
Security accessibility of the overriding method must match the security accessibility of the method being overriden.
متد ISerializable.GetObjectData با ويژگي SecurityCritical در دات نت فريم ورك مزين شده است. با تغييرات امنيتي صورت گرفته در دات نت 4، متدي كه اين متد را تحريف مي‌كند نيز بايد با همان سطح دسترسي متد virtual اصلي معرفي گردد و گرنه برنامه اجرا نخواهد شد. البته اين مشكل ما نيست؛ مشكل سازندگان كتابخانه‌هاي ذكر شده است! ولي خوب تا اين لحظه براي مثال كتابخانه XML RPC براي دات نت 4 به روز نشده است ولي سورس كامل آن در دسترس است.
براي رفع اين مشكل ابتدا سورس اين كتابخانه‌ها را دريافت كرده و سپس در فايل AssemblyInfo.cs آن‌ها يك سطر زير را حذف نموده و پروژه را مجددا كامپايل كنيد:
[assembly: AllowPartiallyTrustedCallers]
علت وجود اين ويژگي‌ در كتابخانه‌هاي ذكر شده اين است كه بتوان از آن‌ها در محيط‌هاي اصطلاحا partially trusted (براي مثال هر برنامه‌اي كه در internet zone يا intranet zone اجرا مي‌شود) استفاده كرد. در دات نت 4 با تغييرات انجام شده معناي AllowPartiallyTrustedCallers به security-transparency تغيير كرده است. بنابراين با قيد آن يا بايد هر جايي كه متد GetObjectData ذكر شده در اين كتابخانه‌ها تحريف مي‌شود، ويژگي SecurityCritical را صريحا اعمال كرد يا اينكه مي‌توان AllowPartiallyTrustedCallers را حذف كرده و وظيفه‌ي انجام آن‌را به CLR محول نمود.


براي مطالعه بيشتر:
Using Libraries from Partially Trusted Code
Security Changes in the .NET Framework 4
TypeLoadException based on Security-Transparent Code, Level 2
Making log4net run on .NET 4.0

۱۳۸۹/۰۸/۱۹

مديريت ساده‌تر امور Async (غير همزمان) در نسخه‌ي بعدي زبان‌هاي دات نتي


چندي قبل مطلبي را در اين سايت در مورد معرفي الگويي كه توسط آن مي‌توان اعمال غير همزمان را به صورت پي در پي انجام داد، مطالعه كرديد:

و بحث اصلي مطالب فوق هم اين است:
"در برنامه نويسي متداول هميشه عادت داريم كه اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممكن است ابتدا C انجام شود، سپس A و بعد B يا هر حالت ديگري صرفنظر از تقدم و تاخر آن‌ها در حين معرفي متدهاي مرتبط در يك قطعه كد. همچنين ميزان خوانايي اين نوع كدنويسي نيز مطلوب نيست...."

خبر خوش آن است كه پشتيباني از اين نوع مدل پي در پي برنامه نويسي در نگارش‌هاي بعدي سي شارپ و VB.NET اضافه شده است.



ليستي از مقالات منتشر شده در اين مورد را در ادامه ملاحظه خواهيد كرد:

علاوه بر آن يك سري ويديوي مرتبط با اين بحث نيز منتشر شده است:

۱۳۸۹/۰۶/۲۷

به روز رساني كتاب Threading in CS


آقاي Albahari (نويسنده برنامه معروف LINQPad) كتاب رايگان خودشون رو در مورد برنامه نويسي چند ريسماني در سي شارپ به روز كرده‌اند كه از آدرس ذيل قابل دريافت است. اين به روز رساني‌ها شامل مباحث اضافه شده در دات نت 4 مانند tasks و غيره كه از مزاياي پردازش موازي بهره مي‌برند نيز مي‌شوند.





۱۳۸۹/۰۳/۱۶

آرگومان‌هاي نامگذاري شده (named arguments/parameters) در C#4 :


سؤال: آيا شما بدون مراجعه به مستندات شيء FormsAuthenticationTicket مي‌توانيد پاسخ دهيد هر كدام از آرگومان‌هاي ذكر شده در سازنده‌ي اين كلاس چه كاربرد و معنايي دارند؟

var ticket = new FormsAuthenticationTicket
(
1,
principal.Identity.Name,
DateTime.Now,
DateTime.Now.AddMinutes(30),
true,
string.Empty,
FormsAuthentication.FormsCookiePath
);

الان چطور؟!
            //using named parameters of C# 4.0
var ticket = new FormsAuthenticationTicket
(
version: 1,
name: principal.Identity.Name,
issueDate: DateTime.Now,
expiration: DateTime.Now.AddMinutes(30),
isPersistent: true,
userData: string.Empty,
cookiePath: FormsAuthentication.FormsCookiePath
);

گاهي از اوقات ويژگي‌هايي كه به يك زبان برنامه نويسي اضافه مي‌شوند راه و روش برنامه نويسي را ممكن است كاملا تحت تاثير قرار دهند؛ براي مثال استفاده از Generics . اما گاهي از اوقات اين ويژگي‌هاي جديد syntax sugar يا nice to have هستند(!) مانند Named arguments .
Named arguments يا آرگومان‌هاي نامگذاري شده خوانايي كدهاي شما را با بيان اينكه هر آرگومان چه معنايي دارد، افزايش مي‌دهند. براي مثال به نظر شما كدام روش فراخواني متد Copy در ذيل خواناتر است؟
File.Copy("source.txt", "destination.txt", true);
و يا؟
File.Copy("source.txt", "destination.txt", overwrite: true);
هر دو يك كار را انجام مي‌دهند اما درك عملكرد روش اول حتما نياز به بررسي مستندات متد Copy را خواهد داشت.

نكات تكميلي:
متد ساده زير را در نظر بگيريد:
static void Foo(int x, int y) {}
الف) در حالت معمولي و بدون استفاده از named argument ، پارامترهاي يك متد، positional parameters هم ناميده مي‌شوند. به اين معنا كه محل ذكر يك پارامتر مهم است و براي مثال در روش متداول فراخواني ذيل، پارامتر دوم هميشه همان y است و پارامتر اول همواره همان x درنظر گرفته مي‌شود:
Foo(1,2);
اما زمانيكه از named parameters استفاده مي‌كنيد، اين ترتيب ديگر اهميتي نداشته و هر دو فراخواني ذيل مجاز مي‌باشند و به يك معنا هستند:
Foo(x:4, y:2);
Foo(y:2, x:4);
ب) همواره positional parameters بايد پيش از named parameters ذكر شوند. براي مثال فراخواني ذيل مجاز است:
Foo(4, y:2);
اما فراخواني زير مجاز نبوده و برنامه كامپايل نخواهد شد:
Foo(x:4, 2);     // Compile time error


۱۳۸۹/۰۳/۰۴

منسوخ شده‌ها در دات نت 4


براي كاهش حجم دات نت در نگارش‌هاي بعدي، كلاس‌هايي كه توسط تيم‌هاي مختلف پيشتر توسعه يافته بودند، اكنون با هم تلفيق شده و نظم بهتري پيدا كرده‌اند. براي مثال كليه كلاس‌هاي مرتبط با زبان‌هاي اسكريپتي اكنون به فضاي نام System.CodeDom.Compiler.ICodeCompiler منتقل شده‌اند و غيره. مرجع نسبتا مفصلي در مورد منسوخ شده‌ها در دات نت 4 اخيرا منتشر شده است:

و حتما مي‌دانيد كه چگونه يك متد يا نوع را بايد به صورت منسوخ شده معرفي كرد:

در اين حالت هنگام كامپايل برنامه، پيغام اخطاري توسط كامپايلر صادر خواهد شد.

۱۳۸۹/۰۲/۲۶

SortedSet در دات نت 4


SortedSet قرار گرفته در فضاي نام System.Collections.Generic دات نت 4، ليستي از اشياء به صورت خودكار مرتب شده را ارائه مي‌دهد. SortedSet نيز همانند HashSet از اعضاي منحصربفردي تشكيل خواهد شد اما اينبار به شكلي مرتب شده. براي پياده سازي آن از red-black tree data structure استفاده شده است كه مهم‌ترين مزيت آن امكان افزودن و يا حذف اشياء به آن بدون كاهش قابل توجه كارآيي برنامه است.

مثال اول:
using System;
using System.Collections.Generic;

namespace SortedSetTest
{
class Program
{
static void sample1()
{
var setRange = new SortedSet<int> { 2, 5, 6, 2, 1, 4, 8 };

foreach (var i in setRange)
{
Console.WriteLine(i);
}
}

static void Main()
{
sample1();
}
}
}
در اين مثال با نحوه‌ي ايجاد اين ليست جنريك خود مرتب شونده‌ي تكراري نپذير (!) آشنا مي‌شويد. اگر اين مثال را اجرا نمائيد، خروجي آن مرتب شده است و همچنين تنها شامل يك عدد 2 است (اعضاي تكراري را حذف مي‌كند).

مثال دوم:

using System;
using System.Collections.Generic;

namespace SortedSetTest
{
class Program
{
static void sample2()
{
var setRange = new SortedSet<int>();
var random = new Random();

for (int counter = 0; counter < 100; counter++)
{
var rnd = random.Next(-180, 181);
if (!setRange.Add(rnd))
{
Console.WriteLine("Couldn't add {0}", rnd);
}
}

Console.WriteLine("Result set:");
foreach (var item in setRange)
{
Console.WriteLine(item);
}
}

static void Main()
{
sample2();
}
}
}
در اين مثال نحوه‌ي افزودن اعضاي مختلف به اين ليست ويژه، توسط متد Add آن بيان شده است. اگر آيتمي در اين ليست موجود باشد، مجددا اضافه نشده و حاصل متد Add آن، False خواهد بود.

مثال سوم:
اگر از ساير انواع سفارشي تعريف شده استفاده نمائيد، بايد روش مقايسه‌ي آن‌ها را نيز با پياده سازي اينترفيس استاندارد IComparable ارائه دهيد؛ در غير اينصورت با خطاي At least one object must implement IComparable متوقف خواهيد شد.

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

namespace SortedSetTest
{
class FileInfo
{
public string Name { set; get; }
public long Size { set; get; }
}

class FileInfoComparer : IComparer<FileInfo>
{
public int Compare(FileInfo x, FileInfo y)
{
var caseiComp = new CaseInsensitiveComparer();
return caseiComp.Compare(x.Name, y.Name);
}
}


class Program
{

static void sample3()
{
var setRange = new SortedSet<FileInfo>(new FileInfoComparer())
{
new FileInfo
{
Name = "file1.txt",
Size = 100
},
new FileInfo
{
Name = "file2.txt",
Size = 10
},
new FileInfo
{
Name = "file3.txt",
Size = 300
}
};

foreach (var item in setRange)
{
Console.WriteLine(item.Name);
}
}

static void Main()
{
sample3();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

در اين مثال اشيايي از نوع كلاس FileInfo به ليست ويژه‌ي ما اضافه شده‌اند. براي اينكه امكان مقايسه‌ي آن‌ها فراهم باشد ، كلاس FileInfoComparer با پياده سازي اينترفيس IComparer ، روش مقايسه دو شيء از اين دست را ارائه مي‌دهد.

۱۳۸۹/۰۲/۲۰

دات نت 4 و كلاس Lazy


يكي از الگوهاي برنامه نويسي شيء گرا، Lazy Initialization Pattern نام دارد كه دات نت 4 پياده سازي آن‌را سهولت بخشيده است.
در دات نت 4 كلاس جديدي به فضاي نام System اضافه شده است به نام Lazy و هدف از آن lazy initialization است؛ من ترجمه‌اش مي‌كنم وهله سازي با تاخير يا به آن on demand construction هم گفته‌اند (زماني كه به آن نياز هست ساخته خواهد شد).
فرض كنيد در برنامه‌ي خود نياز به شيءايي داريد و ساخت اين شيء بسيار پرهزينه است. نيازي نيست تا بلافاصله پس از تعريف، اين شيء ساخته شود و تنها زمانيكه به آن نياز است بايد در دسترس باشد. كلاس Lazy جهت مديريت اينگونه موارد ايجاد شده است. تنها كاري كه در اينجا بايد صورت گيرد، محصور كردن آن شيء هزينه‌بر توسط كلاس Lazy است:

Lazy<ExpensiveResource> ownedResource = new Lazy<ExpensiveResource>();

در اين حالت براي دسترسي به شيء ساخته شده از ExpensiveResource ، مي‌توان از خاصيت Value استفاده نمود (ownedResource.Value). تنها در حين اولين دسترسي به ownedResource.Value ، شيء ExpensiveResource ساخته خواهد شد و نه پيش از آن و نه در اولين جايي كه تعريف شده است. پس از آن اين حاصل cache شده و ديگر وهله سازي نخواهد شد.
ownedResource داراي خاصيت IsValueCreated نيز مي‌باشد و جهت بررسي ايجاد آن شيء مي‌تواند مورد استفاده قرار گيرد. براي مثال قصد داريم اطلاعات ExpensiveResource را ذخيره كنيم اما تنها در حالتيكه يكبار مورد استفاده قرار گرفته باشد.
كلاس Lazy داراي دو متد سازنده‌ي ديگر نيز مي‌باشد:
public Lazy(bool isThreadSafe);
public Lazy(Func<T> valueFactory, bool isThreadSafe);

و هدف از آن استفاده‌ي صحيح از اين متد در محيط‌هاي چند ريسماني است. بديهي است در اين نوع محيط‌ ها علاقه‌اي نداريم كه در يك لحظه توسط چندين ترد مختلف، سبب ايجاد وهله‌هاي ناخواسته‌ا‌ي از ExpensiveResource شويم و تنها يك مورد از آن كافي است يا به قولي thread safe, lazy initialization of expensive objects
بديهي است اگر برنامه‌ي شما چند ريسماني نيست مي‌توانيد اين مكانيزم را كنسل كرده و اندكي كارآيي برنامه را با حذف قفل‌هاي همزماني اين كلاس بالا ببريد.

مثال اول:

using System;
using System.Threading;

namespace LazyExample
{
class Program
{
static void Main()
{
Console.WriteLine("Before assignment");
var slow = new Lazy<Slow>();
Console.WriteLine("After assignment");

Thread.Sleep(1000);

Console.WriteLine(slow);
Console.WriteLine(slow.Value);

Console.WriteLine("Press a key...");
Console.Read();
}
}


class Slow
{
public Slow()
{
Console.WriteLine("Start creation");
Thread.Sleep(2000);
Console.WriteLine("End creation");
}
}
}
خروجي اين برنامه به شرح زير است:

Before assignment
After assignment
Value is not created.
Start creation
End creation
LazyExample.Slow
Press a key...

همانطور كه ملاحظه مي‌كنيد تنها در حالت دسترسي به مقدار Value شيء slow ، عملا وهله‌اي از آن ساخته خواهد شد.

مثال دوم:
شايد نياز به مقدار دهي خواص كلاس پرهزينه‌ وجود داشته باشد. براي مثال علاقمنديم خاصيت SomeProperty كلاس ExpensiveClass را مقدار دهي كنيم. براي اين منظور مي‌توان به شكل ذيل عمل كرد (يك Func<t>را مي‌توان به سازنده‌ي آن ارسال نمود):

using System;

namespace LazySample
{
class Program
{
static void Main()
{
var expensiveClass =
new Lazy<ExpensiveClass>
(
() =>
{
var fobj = new ExpensiveClass
{
SomeProperty = 100
};
return fobj;
}
);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("expensiveClass.SomeProperty value {0}",
(expensiveClass.Value).SomeProperty);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("Press a key...");
Console.Read();
}
}

class ExpensiveClass
{
public int SomeProperty { get; set; }

public ExpensiveClass()
{
Console.WriteLine("ExpensiveClass constructed");
}
}
}

كاربردها:
- علاقمنديم تا ايجاد يك شيء هزينه‌بر تنها پس از انجام يك سري امور هزينه‌بر ديگر صورت گيرد. براي مثال آيا تابحال شده است كه با يك سيستم ماتريسي كار كنيد و نياز به چند گيگ حافظه براي پردازش آن داشته باشيد؟! زمانيكه از كلاس Lazy استفاده نمائيد، تمام اشياء مورد استفاده به يكباره تخصيص حافظه پيدا نكرده و تنها در زمان استفاده از آن‌ها كار تخصيص منابع صورت خواهد گرفت.
- ايجاد يك شيء بسيار پر هزينه بوده و ممكن است در بسياري از موارد اصلا نيازي به ايجاد و يا حتي استفاده از آن نباشد. براي مثال يك شيء كارمند را درنظر بگيريد كه يكي از خواص اين شيء، ليستي از مكان‌هايي است كه اين شخص قبلا در آنجاها كار كرده است. اين اطلاعات نيز به طور كامل از بانك اطلاعاتي دريافت مي‌شود. اگر در متدي، استفاده كننده از شيء كارمند هيچگاه اطلاعات مكان‌هاي كاري قبلي او را مورد استفاده قرار ندهد، آيا واقعا نياز است كه اين اطلاعات به ازاي هر بار ساخت وهله‌اي از شيء كارمند از ديتابيس دريافت شده و همچنين در حافظه ذخيره شود؟

۱۳۸۹/۰۲/۱۷

Tuple در دات نت 4


نوع جديدي در دات نت 4 به نام Tuple اضافه شده است كه در اين مطلب به بررسي آن خواهيم پرداخت.
در رياضيات، Tuple به معناي ليست مرتبي از اعضاء با تعداد مشخص است. Tuple در زبان‌هاي برنامه نويسي Dynamic مانند اف شارپ، Perl ، LISP و بسياري موارد ديگر مطلب جديدي نيست. در زبان‌هاي dynamic برنامه نويس‌ها مي‌توانند متغيرها را بدون معرفي نوع آن‌ها تعريف كنند. اما در زبان‌هاي Static مانند سي شارپ، برنامه نويس‌ها موظفند نوع متغيرها را پيش از كامپايل آن‌ها معرفي كنند كه هر چند كار كد نويسي را اندكي بيشتر مي‌كند اما به اين صورت شاهد خطاهاي كمتري نيز خواهيم بود (البته سي شارپ 4 اين مورد را با معرفي واژه‌ي كليدي dynamic تغيير داده است).
براي مثال در اف شارپ داريم:
let data = (“John Doe”, 42)

كه سبب ايجاد يك tuple كه المان اول آن يك رشته و المان دوم آن يك عدد صحيح است مي‌شود. اگر data را بخواهيم نمايش دهيم خروجي آن به صورت زير خواهد بود:
printf “%A” data
// Output: (“John Doe”,42)

در دات نت 4 فضاي نام جديدي به نام System.Tuple معرفي شده است كه در حقيقت ارائه دهنده‌ي نوعي جنريك مي‌باشد كه توانايي در برگيري انواع مختلفي را دارا است :
public class Tuple<T1>
up to:
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>

همانند آرايه‌ها، اندازه‌ي Tuples نيز پس از تعريف قابل تغيير نيستند (immutable). اما تفاوت مهم آن با يك آرايه در اين است كه اعضاي آن مي‌توانند نوع‌هاي كاملا متفاوتي داشته باشند. همچنين تفاوت مهم آن با يك ArrayList يا آرايه‌اي از نوع Object، مشخص بودن نوع هر يك از اعضاء آن است كه type safety بيشتري را به همراه خواهد داشت و كامپايلر مي‌تواند در حين كامپايل دقيقا مشخص نمايد كه اطلاعات دريافتي از نوع صحيحي هستند يا خير.

يك مثال كامل از Tuples را در كلاس زير ملاحظه خواهيد نمود:

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

namespace TupleTest
{
class TupleCS4
{
#region Methods (4)

// Public Methods (4)

public static Tuple<string, string> GetFNameLName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new NullReferenceException("name is empty.");

var nameParts = name.Split(',');

if (nameParts.Length != 2)
throw new FormatException("name must contain ','");

return Tuple.Create(nameParts[0], nameParts[1]);
}

public static void PrintSelectedTuple()
{
var list = new List<Tuple<string, int>>
{
new Tuple<string, int>("A", 1),
new Tuple<string, int>("B", 2),
new Tuple<string, int>("C", 3)
};

var item = list.Where(x => x.Item2 == 2).SingleOrDefault();
if (item != null)
Console.WriteLine("Selected Item1: {0}, Item2: {1}",
item.Item1, item.Item2);
}

public static void PrintTuples()
{
var tuple1 = new Tuple<int>(12);
Console.WriteLine("tuple1 contains: item1:{0}", tuple1.Item1);

var tuple2 = Tuple.Create("Item1", 12);
Console.WriteLine("tuple2 contains: item1:{0}, item2:{1}",
tuple2.Item1, tuple2.Item2);

var tuple3 = Tuple.Create(new DateTime(2010, 5, 6), "Item2", 20);
Console.WriteLine("tuple3 contains: item1:{0}, item2:{1}, item3:{2}",
tuple3.Item1, tuple3.Item2, tuple3.Item3);
}

public static void Tuple8()
{
var tup =
new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>
(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int>(8, 9));

Console.WriteLine("tup.Rest Item1: {0}, Item2: {1}",
tup.Rest.Item1,tup.Rest.Item2);
}

#endregion Methods
}
}

using System;

namespace TupleTest
{
class Program
{
static void Main()
{
var data = TupleCS4.GetFNameLName("Vahid, Nasiri");
Console.WriteLine("Data Item1:{0} & Item2:{1}",
data.Item1, data.Item2);

TupleCS4.PrintTuples();

TupleCS4.PrintSelectedTuple();

TupleCS4.Tuple8();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

توضيحات :
- روش‌هاي متفاوت ايجاد Tuples را در متد PrintTuples مي‌توانيد ملاحظه نمائيد. همچنين نحوه‌ي دسترسي به مقادير هر كدام از اعضاء نيز مشخص شده است.
- كاربرد مهم Tuples در متد GetFNameLName نمايش داده شده است؛ زمانيكه نياز است تا چندين خروجي از يك تابع داشته باشيم. به اين صورت ديگر نيازي به تعريف آرگومان‌هايي به همراه واژه كليدي out نخواهد بود يا ديگر نيازي نيست تا يك شيء جديد را ايجاد كرده و خروجي را به آن نسبت دهيم. به همان سادگي زبان‌هاي dynamic در اينجا نيز مي‌توان يك tuple را ايجاد و استفاده كرد.
- بديهي است از Tuples در يك ليست جنريك و يا حالات ديگر نيز مي‌توان استفاده كرد. مثالي از اين دست را در متد PrintSelectedTuple ملاحظه خواهيد نمود. ابتدا يك ليست جنريك از Tuple ايي با دو عضو تشكيل شده است. سپس با استفاده از امكانات LINQ ، عضوي كه آيتم دوم آن مساوي 2 است يافت شده و سپس المان‌هاي آن نمايش داده مي‌شود.
- نكته‌ي ديگري را كه حين كار با Tuples مي‌توان در نظر داشت اين است كه اعضاي آن حداكثر شامل 8 عضو مي‌توانند باشند كه عضو آخر بايد يك Tuple تعريف گردد و بديهي است اين Tuple‌ نيز مي‌تواند شامل 8 عضو ديگر باشد و الي آخر كه نمونه‌اي از آن را در متد Tuple8 مي‌توان مشاهده كرد.

۱۳۸۹/۰۲/۰۹

سري فيبوناچي و دات نت 4 !


سري معروف فيبوناچي كه معرف حضور شما هست. سري از اعداد است كه هر عدد آن مساوي حاصل جمع دو عدد ماقبل آن است. دو عدد اول اين سري هم 0 و 1 هستند.
اگر بخواهيم اين الگوريتم را به صورت يك متد بازگشتي نمايش دهيم به صورت زير خواهد بود:

public static int Fibonacci(int x)
{
if (x <= 1)
return 1;
return Fibonacci(x - 1) + Fibonacci(x - 2);
}

اين الگوريتم چند مشكل دارد:
الف) براي اعداد بزرگ حتي با بكارگيري Int64 و يا double و امثال آن هم باز به جواب نخواهيم رسيد (براي مثال 1500 را بررسي كنيد).
ب) بسيار كند است.

در دات نت 4 براي كار با اعداد بزرگ، فضاي نام System.Numerics معرفي شده است كه حاوي نوع جديدي از اعداد به نام BigInteger است.
اكنون اگر الگوريتم سري فيبوناچي را بر اساس اين نوع داده جديد بازنويسي كنيم خواهيم داشت:

using System;
using System.Collections.Generic;
using System.Numerics; //needs a ref. to this assembly

namespace Fibonaci
{
public class CFibonacci
{
public static int Fibonacci(int x)
{
if (x <= 1)
return 1;
return Fibonacci(x - 1) + Fibonacci(x - 2);
}

public static IEnumerable<BigInteger> BigFib(Int64 toNumber)
{
BigInteger previous = 0;
BigInteger current = 1;

for (Int64 y = 1; y <= toNumber; y++)
{
var auxiliar = current;
current += previous;
previous = auxiliar;
yield return current;
}
}
}
}
و مثالي در مورد نحوه استفاده از آن:

using System;
using System.Linq;

namespace Fibonaci
{
class Program
{
static void Main()
{
foreach (var i in CFibonacci.BigFib(10))
{
Console.WriteLine("{0}", i);
}

var num = 12000;
var fib = CFibonacci.BigFib(num).Last();
Console.WriteLine("fib({0})={1}", num, fib);

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

براي نمونه با عدد 12000 خروجي برنامه در كسري از ثانيه (و نه چند دقيقه يا ساعت) به شرح زير خواهد بود:

fib(12000)=514263424911336592579396579289954520826834443526829600435873863248622
65414020714013892551476261070010099275571144059579167356039437242089427136323689
02207956221569622791450891447905907668251232675988098246382902426783148546665404
47372384043164600945249911273857878346679362876357499204290285069442042444471200
52292329349103672302428662317285015525888210397583707071480178840772972692357054
71823998861896761687119434646250991702691100894769561810834542099577336821493905
41651658937860506067011215222435859797671748514023462634575877112541265857011723
31453990415231608729534781720381122965899871018532003735284559342372552627132300
63895825396012087948050855095233633638445668687440232926253620457459973889510838
23542785159371236389909470974738599166720611351903568781845409425624666559791912
02212289710838873334773835118391287956725504426150461421914844191810523257658770
99885492757927034409234340065928400769741802132203888929463702342324148343605275
28928280472094493359682662519127203581813404104542972181231076224891404730611459
03321942693225066038987483163709402601230467054944349111055850348779989058517069
96087626795709205215727843443054577680024507650678240240742421270422674907476927
22422733945450760323640619100021663675080870429299040891840880753646474330069332
72320218334582268219906763463261387161318500503970491314781100556494361063341371
43577787961183154125538371204296752028496084633103476783071177779604042581017888
28257784920659671082363171157289668904381254080676855815524987553372657063695970
39668109161449140707240711279859427919912443872405284305891366802954763421905970
15206311458187449420118838775707435857999310870199585760807680179258273461000460
97527064929564528474349547038178370043823628944670926601955537657427194815893365
88494863101667547896798728140224921584809355334379707156342620570496834086358692
30946467203330676206265047960072392991634456381998479411463182171816379650120684
35082399788137090460167819041845511951296934273988759169877839532492294430334328
46972905198131530224288922834125154211248159843609629469051889033085360540770480
25633451201705370447586177546577777759300410144166197439355903631773088812515215
09638377918595294747887970034209028019490210394392422302403687059119407005858379
52137098994457236290005745735420803758853723206992134642997705010940581386168427
47382973672816710014652632509888958851675894223117421829434728942878605569971512
65291783384910157203679779458354245579846973830472593370160977523707902575129803
072039857524154149354311250529579592001

۱۳۸۹/۰۱/۰۱

دريافت فايل از يوتيوب


سلام

سال نو مبارك! به اميد سالي بهتر از پارسال!

اين روزها با هزينه‌‌اي معادل هزينه‌ي تهيه‌ي يك هاست اشتراكي ساليانه براي بالاگذاري يك سايت معمولي در 5 سال قبل، مي‌توان يك VPS تهيه كرد و به اين صورت قفل و كليد يك نيمچه سرور را (با 200 و خرده‌اي مگ رم، 30 گيگ فضا، سرعت CPU نزديك به 700 MHz و ويندوز سرور 2003 يا 2008) در اختيار شما قرار مي‌دهند (البته به قول معروف هر چقدر پول بدهيد همانقدر هم سخت افزار در اختيار شما قرار مي‌دهند) بجاي صرفا يك دايركتوري مجازي محدود با 100 مگ فضاي هاست كه هر احدي در آن هاست اشتراكي مي‌تواند سر مبارك را اندكي چرخانده و تمام زندگي شما را مرور كند و غيره!
استفاده‌ي مفيدي هم كه اين VPS براي من داشته، ترنس لود كردن يك سري فايل است (با توجه به سرعت‌هاي نجومي دريافت فايل اين سرورها). براي مثال دريافت فايل از يوتيوب و انتقال به يك هاست ديگر براي دريافت ساده‌تر خودم و يا ديگران.
براي نمونه سايت dotnet-tv.com را در نظر بگيريد. تعدادي از ويديوهاي اين سايت در يوتيوب هاست شده و از اين دست زياد هستند. خيلي‌ها براي فرار از مشكلات كمبود پهناي باند از يوتيوب استفاده مي‌كنند. يوتيوب هم كه از اين طرف بسته است. خوب، من الان مي‌خواهم ويديوي مربوط به ASP.Net MVC آن را مشاهده كنم، چكار بايد كرد؟!
يك برنامه‌ي ساده‌ي كنسول را تهيه كرده‌ام كه اين كار را براي VPS‌ داران تسهيل مي‌كند.
- دريافت فايل از يوتيوب
- آپلود خودكار آن به رپيدشير


يك نمونه خروجي آن: (فايل‌هاي يوتيوب سايت ذكر شده كه به رپيدشير منتقل شده)
دريافت

در سورس اين برنامه موارد زير پياده سازي شده است:
- يافتن لينك‌هاي يوتيوب سايت dotnet-tv.com با استفاده از regular expressions
- يافتن لينك دانلود مستقيم اين فايل‌ها از سايت يوتيوب كه شامل استفاده از regular expressions براي استخراج قسمت‌هاي مفيد از صفحات و همچنين استفاده از امكانات Json دات نت فريم ورك سه و نيم براي parse قسمت‌هاي استخراج شده است.
- ايجاد يك thread pool سفارشي كه هر بار 7 لينك مستقيم را به صورت همزمان از يوتيوب دريافت مي‌كند. (thread pool پيش فرض دات نت تمام تردها را به يكباره شروع مي‌كند كه براي اينكار مفيد نيست. به همين جهت از اين thread pool سفارشي شده استفاده شد)
پيش فرض فايلي كه از سايت يوتيوب دريافت مي‌شود MP4 با كيفيت بالا است كه با fmt=18 در فايل Youtube.cs مشخص شده. فرمت‌هاي ديگر را مي‌توانيد از اين فايل ايده بگيريد.
- آپلود فايل دريافتي از يوتيوب به يك اكانت رايگان كالكتور در رپيدشير. (ماخذ اين مورد در سايت code projects)
مشخصات اين اكانت رايگان كالكتور در فايل app.config بايد ذكر شود.

اين سورس مي‌تونه ايده‌ي ابتدايي بسياري از كارهاي مشابه باشد. براي مثال ايجاد يك وب سرويس، يك وب سايت، يك سرويس ايميلي و غيره.



پ.ن.
كار انجام شده فعاليت وارز محسوب نمي‌شود زيرا مجوز ويديوهاي سايت يوتيوب اين امكان توزيع (و بسياري موارد ديگر) را به شما مي‌دهد.

۱۳۸۸/۰۷/۱۱

رهنمودهاي كد نويسي سي شارپ 3


دو فايل زير مقاله و خلاصه مقاله‌اي در مورد روش‌هاي بهتر كد نويسي با سي شارپ 3 هستند.


اين رهنمودها (و نه استانداردها) جهت بالا بردن كيفيت كد‌هاي تهيه شده، يك دست شدن آن‌ها در يك سازمان، تهيه مستندات بهتر و امكان نگهداري ساده‌تر آن‌ها، بسيار مؤثرند.
تعدادي از آن‌ها را در مقاله‌ي "زيباتر كد بنويسيم" ديده‌ايد. مقالات فوق گردآوري و به روز رساني اينگونه نكات جهت پوشش دادن سي شارپ 3 مي‌باشند.

ماخذ


۱۳۸۸/۰۶/۲۸

بررسي واژه كليدي static


تفاوت بين يك كلاس استاتيك، متدي استاتيك و يا متغير عضو استاتيك چيست؟ چه زماني بايد از آن‌ها‌ استفاده كرد و لزوم بودن آن‌ها‌ چيست؟
براي پاسخ دادن به اين سؤالات بايد از نحوه‌ي تقسيم بندي حافظه شروع كرد.
RAM براي هر نوع پروسه‌اي كه در آن بارگذاري مي‌شود به سه قسمت تقسيم مي‌گردد: Stack ، Heap و Static (استاتيك در دات نت در حقيقت قسمتي از Heap است كه به آن High Frequency Heap نيز گفته مي‌شود).
اين قسمت استاتيك حافظه، محل نگهداري متدها و متغيرهاي استاتيك است. آن متدها و يا متغيرهايي كه نياز به وهله‌اي از كلاس براي ايجاد ندارند، به صورت استاتيك ايجاد مي‌گردند. در سي شارپ از واژه كليدي static براي معرفي آن‌ها كمك گرفته مي‌شود. براي مثال:

class MyClass
{
public static int a;
public static void DoSomething();
}
در اين مثال براي فراخواني متد DoSomething نيازي به ايجاد يك وهله جديد از كلاس MyClass نمي‌باشد و تنها كافي است بنويسيم:

MyClass.DoSomething(); // and not -> new MyClass().DoSomething();
نكته‌ي مهمي كه در اينجا وجود دارد اين است كه متدهاي استاتيك تنها قادر به استفاده از متغيرهاي استاتيك تعريف شده در سطح كلاس هستند. علت چيست؟
به مثال زير دقت نمائيد:

class MyClass
{
// non-static instance member variable
private int a;
//static member variable
private static int b;
//static method
public static void DoSomething()
{
//this will result in compilation error as “a” has no memory
a = a + 1;
//this works fine since “b” is static
b = b + 1;
}
}
در اين مثال اگر متد DoSomething را فراخواني كنيم، تنها متغير b تعريف شده، در حافظه حضور داشته (به دليل استاتيك معرفي شدن) و چون با روش فراخواني MyClass.DoSomething هنوز وهله‌اي از كلاس مذكور ايجاد نشده، به متغير a نيز حافظه‌اي اختصاص داده نشده است و نامعين مي‌باشد.
بر اين اساس كامپايلر نيز از كامپايل شدن اين كد جلوگيري كرده و خطاي لازم را گوشزد خواهد كرد.

اكنون تعريف يك كلاس به صورت استاتيك چه اثري را خواهد داشت؟
با تعريف يك كلاس به صورت استاتيك مشخص خواهيم كرد كه اين كلاس تنها حاوي متدها و متغيرهاي استاتيك مي‌باشد. امكان ايجاد يك وهله از آن‌ها وجود نداشته و نيازي نيز به اين امر ندارند. اين كلاس‌ها امكان داشتن instance variables را نداشته و به صورت پيش فرض از نوع sealed به حساب خواهند آمد و امكان ارث بري از آن‌ها نيز وجود ندارد. علت اين امر هم اين است كه يك كلاس static هيچ نوع رفتاري را تعريف نمي‌كند.

پس با اين تفاسير چرا نياز به يك كلاس static ممكن است وجود داشته باشد؟
همانطور كه عنوان شد يك كلاس استاتيك هيچ نوع رفتاري را تعريف نمي‌كند بنابراين بهترين مكان است براي تعريف متدهاي كمكي كه به ساير اعضاي كلاس‌هاي ما وابستگي نداشته، عمومي بوده، مستقل و متكي به خود هستند. عموما متدهاي كمكي در يك برنامه به صورت مكرر فراخواني شده و نياز است تا به سرعت در دسترس قرار داشته باشند و حداقل يك مرحله ايجاد وهله كلاس در اينجا براي راندمان بيشتر حذف گردد.
براي مثال متدي را در نظر بگيريد كه بجز اعداد، ساير حروف يك رشته را حذف مي‌كند. اين متد عمومي است، وابستگي به ساير اعضاي يك كلاس يا كلاس‌هاي ديگر ندارد. بنابراين در گروه متدهاي كمكي قرار مي‌گيرد. اگر از افزونه‌ي ReSharper‌ استفاده نمائيد، اين نوع متدها را به صورت خودكار تشخيص داده و راهنمايي لازم را جهت تبديل آ‌ن‌ها به متد‌هاي استاتيك ارائه خواهد داد.

با كلاس‌هاي استاتيك نيز همانند ساير كلاس‌هاي يك برنامه توسط JIT compiler رفتار مي‌شود، اما با يك تفاوت. كلاس‌هاي استاتيك فقط يكبار هنگام اولين دسترسي به آن‌ها ساخته شده و در قسمت High Frequency Heap حافظه قرار مي‌گيرند. اين قسمت از حافظه تا پايان كار برنامه از دست garbage collector‌ در امان است (بر خلاف garbage-collected heap‌ يا object heap كه جهت instance classes مورد استفاده قرار مي‌گيرد)


نكته:
در برنامه‌هاي ASP.Net از بكارگيري متغيرهاي عمومي استاتيك برحذر باشيد (از static fields و نه static methods). اين متغيرها بين تمامي كاربران همزمان يك برنامه به اشتراك گذاشته شده و همچنين بايد مباحث قفل‌گذاري و امثال آن‌را در محيط‌هاي چند ريسماني هنگام كار با آن‌ها رعايت كرد (thread safe نيستند).

۱۳۸۸/۰۶/۲۵

كامپايل پوياي كد در دات نت


در دات نت فريم ورك امكان كامپايل پوياي يك قطعه كد دريافت شده از يك رشته، توسط فضاي نام CodeDom مهيا است كه قدرت قابل توجهي را در اختيار برنامه نويس قرار مي‌دهد.

مثال يك:
رشته زير را كامپايل كرده و تبديل به يك فايل exe كنيد:

string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
روش انجام كار به همراه توضيحات مربوطه به صورت كامنت:

using System;
using System.Collections.Generic;
//دو فضاي نامي كه براي اين منظور اضافه شده‌اند
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace compilerTest
{
class Program
{
static void compileIt1()
{
//سورس كد ما جهت كامپايل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعيين نگارش كامپايلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعيين اينكه كد ما سي شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//تعيين اينكه خروجي يك فايل اجرايي است بعلاوه مشخص سازي محل ذخيره سازي فايل نهايي
CompilerParameters compilerParams = new CompilerParameters
{
OutputAssembly = "D:\\Foo.EXE",
GenerateExecutable = true
};

//عمليات كامپايل در اينجا صورت مي‌گيرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

//اگر خطايي وجود داشته باشد نمايش داده خواهد شد
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}

static void Main(string[] args)
{
compileIt1();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مثال 2:
كد مورد نظر را به صورت يك فايل dll كامپايل كنيد.
براي اين منظور تمامي مراحل مانند قبل است فقط GenerateExecutable ذكر شده به false تنظيم شده و نام خروجي نيز به foo.dll بايد تنظيم شود.


مثال 3:
كد مورد نظر را در حافظه كامپايل كرده (خروجي dll يا exe نمي‌خواهيم)، سپس متد SayHello آن را به صورت پويا فراخواني نموده و خروجي را نمايش دهيد.
در اين حالت روش كار همانند مثال 1 است با اين تفاوت كه GenerateInMemory = true و GenerateExecutable = false تنظيم مي‌شوند. همچنين جهت دسترسي به متد كلاس ذكر شده،‌ از قابليت‌هاي ريفلكشن موجود در دات نت فريم ورك استفاده خواهد شد.

using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace compilerTest
{
class Program
{
static void compileIt2()
{
//سورس كد ما جهت كامپايل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعيين نگارش كامپايلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعيين اينكه كد ما سي شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//نحوه تعيين مشخص سازي كامپايل در حافظه
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};

//عمليات كامپايل در اينجا صورت مي‌گيرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

// اگر خطايي در كامپايل وجود نداشت متد دلخواه را فراخواني مي‌كنيم
if (results.Errors.Count == 0)
{
//استفاده از ريفلكشن براي دسترسي به متد و فراخواني آن
Type type = results.CompiledAssembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}


static void Main(string[] args)
{
compileIt2();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
نكته: نحوه‌ي استفاده از اسمبلي‌هاي ديگر در رشته سورس كد خود
مثال:
اگر رشته سورس ما به صورت زير بوده و از اسمبلي System.Drawing.Dll نيز كمك گرفته باشد،‌

string source =
@"
namespace Foo
{

public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
var r = new System.Drawing.Rectangle(0,0,100,100);
System.Console.WriteLine(r);
}
}
}
";
هنگام كامپايل آن توسط روش مثال يك، با خطاي زير مواجه خواهيم شد.

Number of Errors: 1
ERROR The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)

براي رفع اين مشكل و معرفي اين اسمبلي،‌ سطر زير بايد پس از تعريف compilerParams اضافه شود.

compilerParams.ReferencedAssemblies.Add("System.Drawing.Dll");
اكنون كد كامپايل شده و مشكلي نخواهد داشت.
نمونه‌اي ديگر از اين دست، استفاده از LINQ مي‌باشد. در اين حالت اسمبلي System.Core.Dll نيز به روش ذكر شده بايد معرفي گردد تا مشكلي در كامپايل كد رخ ندهد.


كاربردها:
1- استفاده در ابزارهاي توليد كد (براي مثال در برنامه Linqer از اين قابليت استفاده مي‌شود)
2- استفاده‌هاي امنيتي (ايجاد روش‌هاي توليد يك سريال به صورت پويا و كامپايل پوياي كد مربوطه در حافظه‌اي محافظت شده)
3- استفاده جهت مقاصد محاسباتي پيشرفته
4- دادن اجازه‌ي كد نويسي به كاربران برنامه‌ي خود (شبيه به سيستم‌هاي ماكرو و اسكريپت نويسي موجود)
و ...