مطالب
CoffeeScript #7

اصطلاحات عمومی CoffeeScript

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

Each

در جاوااسکریپت وقتی می‌خواهیم بر روی آرایه‌ای با بیش از یک خانه، کاری را چندین بار انجام دهیم، می‌توانیم از تابع ()forEach یا از همان قالب حلقه‌ی for در زبان C استفاده کنیم:

for (var i=0; i < array.length; i++)
  myFunction(array[i]);

array.forEach(function(item, i){
  myFunction(item)
});
اگرچه تابع ()forEach مختصر و خواناتر است ولی یک مشکل دارد؛ به دلیل فراخوانی تابع callback در هر بار اجرای حلقه، بسیار کندتر از حلقه for اجرا می‌شود.
حال به نحوه‌ی کارکرد CoffeeScript دقت کنید.
myFunction(item) for item in array
که پس از کامپایل می‌شود:
var i, item, len;

for (i = 0, len = array.length; i < len; i++) {
  item = array[i];
  myFunction(item);
}
همانطوری که مشاهده می‌کنید، از نظر syntax بسیار ساده و با خوانایی بالا است و مطمئن هستم شما هم با من موافق هستید و نکته‌ی مهمی که وجود دارد، کامپایل حلقه‌ی با ظاهر forEach به حلقه‌ی for، توسط CoffeeScript و حفظ سرعت اجرای آن است.

Map

همانند تابع forEach که در استاندارد ES5 قرار داشت، تابع دیگری به نام ()map وجود دارد که از نظر syntax بسیار خلاصه‌تر از حلقه‌ی for می‌باشد. ولی متاسفانه همانند تابع forEach، این تابع نیز به دلیل فراخوانی تابع، بسیار کندتر از for اجرا می‌شود.

var result = []
for (var i=0; i < array.length; i++)
  result.push(array[i].name)

var result = array.map(function(item, i){
  return item.name;
});
همانطور که مشاهده می‌کنید در اینجا طریقه‌ی استفاده از تابع map و پیاده سازی آن بدون استفاده از تابع map نشان داده شده است. حال به مثال زیر توجه کنید:
result = (item.name for item in array)
با استفاده از ساختار حلقه‌ها که در قسمت 4 گفتیم و تنها با قراردادن () در اطراف آن می‌توان تابع map را به راحتی پیاده سازی کرد.
نتیجه‌ی کامپایل مثال بالا می‌شود:
var item, result;

result = (function() {
  var i, len, results;
  results = [];
  for (i = 0, len = array.length; i < len; i++) {
    item = array[i];
    results.push(item.name);
  }
  return results;
})();

Select

یکی دیگر از توابع ES5، تابع ()filter است که برای کاهش خانه‌های آرایه استفاده می‌شود.

var result = []
for (var i=0; i < array.length; i++)
  if (array[i].name == "test")
    result.push(array[i])

result = array.filter(function(item, i){
  return item.name == "test"
});
CoffeeScript با استفاده از کلمه‌ی کلیدی when، عمل فیلتر کردن آیتم‌هایی را که نمی‌خواهیم در آرایه باشند، انجام می‌دهد و در پشت صحنه، با استفاده از یک حلقه‌ی for این عمل را انجام می‌دهد.
result = (item for item in array when item.name is "test")
در اینجا نیز همانند تابع map برای جلوگیری از تداخل متغیرها از یک تابع بی‌نام استفاده می‌کند.
var item, result;

result = (function() {
  var i, len, results;
  results = [];
  for (i = 0, len = array.length; i < len; i++) {
    item = array[i];
    if (item.name === "test") {
      results.push(item);
    }
  }
  return results;
})();

نکته‌ی مهم: در صورت فراموشی اضافه کردن () در اطراف حلقه‌ی نوشته شده، نتیجه‌ی صحیحی تولید نخواهد شد و نتها آخرین عضو از خروجی را باز می‌گرداند.
var i, item, len, result;

for (i = 0, len = array.length; i < len; i++) {
  item = array[i];
  if (item.name === "test") {
    result = item;
  }
}

قوه‌ی درک CoffeeScript بسیار بالا و انعطاف پذیر است، به مثال زیر توجه کنید:
passed = []
failed = []
(if score > 60 then passed else failed).push score for score in [49, 58, 76, 82, 88, 90]

# Or
passed = (score for score in scores when score > 60)
و یا در صورتیکه طول خط نوشته شده زیاد باشد می‌توانید به صورت چند خطی آن را بنویسید:
passed = []
failed = []
for score in [49, 58, 76, 82, 88, 90]
  (if score > 60 then passed else failed).push score
و نتیجه‌ی کامپایل مثال آخر می‌شود:
var failed, i, len, passed, ref, score;

passed = [];

failed = [];

ref = [49, 58, 76, 82, 88, 90];
for (i = 0, len = ref.length; i < len; i++) {
  score = ref[i];
  (score > 60 ? passed : failed).push(score);
}
مطالب
CoffeeScript #2

Syntax

برای کار با CoffeeScript، ابتدا باید با ساختار Syntax آن آشنا شد. CoffeeScript در بسیاری از موارد با جاوااسکریپت یکسان است در حالیکه در قسمت قبل گفته شد که CoffeeScript زیر مجموعه‌ای جاوااسکریپت نیست؛ بنابراین برخی از کلمات کلیدی مانند function و var در آن مجاز نیست و سبب بروز خطا در زمان کامپایل می‌شوند. وقتی شما شروع به نوشتن فایل CoffeeScript می‌کنید، باید تمام کدهایی را که می‌نویسید، با Syntax کامل CoffeeScript بنویسید و نمی‌توانید قسمتی را با جاوااسکریپت و قسمتی را با CoffeeScript بنویسید.

برای نوشتن توضیحات در فایل CoffeeScript باید از علامت # استفاده کنید که این قسمت را از زبان Ruby گرفته است.

# A comment
در صورتیکه نیاز به نوشتن توضیحات را در چندین خط داشته باشید نیز این امکان دیده شده است:
###
  A multiline comment
###
نکته: تفاوتی که در توضیح یک خطی و چند خطی وجود دارد این است که توضیحات چند خطی پس از کامپایل، در فایل جاوااسکریپت خروجی نوشته می‌شوند، ولی توضیحات یک خطی در فایل خروجی تولید می‌شود.

در زبان CoffeeScript فاصله (space) بسیار مهم است؛ چرا که زبان Python براساس میزان تو رفتگی کدها، بدنه‌ی شرط‌ها و حلقه‌ها را تشخیص می‌دهد و CoffeeScript نیز از این ویژگی استفاده می‌کند. هرگاه بخواهید از {} استفاده کنید فقط کافی است از کلید Tab استفاده کنید تا پس از کامپایل به صورت {} تبدیل شود.

Variables & Scope

CoffeeScript یکی از باگهایی را که در نوشتن جاوااسکریپت وجود دارد (متغیرهای سراسری) حل کرده است. در جاوااسکریپت درصورتیکه هنگام تعریف متغیری از کلمه‌ی کلیدی var در پشت اسم متغیر استفاده نشود، به صورت سراسری تعریف می‌شود. CoffeeScript به سادگی متغیرهای سراسری را حذف می‌کند. در پشت صحنه‌ی این حذف، اسکریپت نوشته شده را درون یک تابع بدون نام قرار می‌دهد و با این کار تمامی متغیرها در ناحیه‌ی محلی قرار می‌گیرند و سپس قبل از نام هر متغیری، کلمه‌ی کلیدی var را قرار می‌دهد. برای مثال:

myVariable = "vahid"
که نتیجه کامپایل آن می‌شود:
var myVariable;
myVariable = "vahid";
همان طور که مشاهده می‌کنید، متغیر تعریف شده به صورت محلی تعریف شده و با این روش تعریف متغیر سراسری را به صورت اشتباهی، غیرممکن می‌کند. این روش استفاده شده در CoffeeScript جلوی بسیاری از اشتباهات معمول توسعه دهندگان وب را می‌گیرد.
با این حال گاهی اوقات نیاز است که متغیر سراسری تعریف کنید. برای اینکار باید از شیء سراسری موجود در مرورگر (window) یا از روش زیر استفاده کنید:
exports = this
exports.MyVariable = "vahid"


Functions

CoffeeScript برای راحتی در نوشتن توابع، کلمه کلیدی function را حذف کرده و به جای آن از <- استفاده می‌کند. توابع در CoffeeScript می‌توانند در یک خط یا به صورت تورفته در چندین خط نوشته شده باشند. آخرین عبارتی که در یک تابع نوشته می‌شود به صورت ضمنی بازگشت داده می‌شود. در صورتیکه نیاز به بازگرداندن مقداری در تابع ندارید، از کلمه‌ی return به تنهایی استفاده کنید.

func = -> "vahid"
نتیجه‌ی کامپایل آن می‌شود:
var func;
func = function() {
  return "vahid";
};
همان طور که در بالا گفته شده، در صورتیکه بخواهید تابعی با چندین خط دستور داشته باشید، باید ساختار تو رفتگی را حفظ کرد. برای مثال:
func = ->
  # An extra line
  "vahid"
نتیجه کامپایل کد بالا نیز همانند کد قبلی می‌باشد.

Function arguments

برای تعریف آرگومان در توابع باید قبل از <- از () استفاده کرد و آرگومان هایی را که نیاز است، در داخل آن تعریف کرد. برای مثال:

func = (a, b) -> a * b
نتیجه‌ی کامپایل آن می‌شود:
var func;
func = function(a, b) {
  return a * b;
};
CoffeeScript از مقدار پیش فرض برای آرگومان‌های توابع نیز پشتیبانی می‌کند:
func = (a = 1, b = 2) -> a * b
همچنین در صورتیکه تعداد آرگومان‌های یک تابع برای شما مشخص نبود، می‌توانید از " ... "  استفاده کنید. مثلا وقتی می‌خواهید جمع n عدد را بدست آورید که n عدد به صورت آرگومان به تابع ارسال می‌شوند:
sum = (nums...) -> 
  result = 0
  nums.forEach (n) -> result += n
  result
در مثال فوق آرگومان nums آرایه‌ای از تمام آرگومان‌های ارسال شده به تابع است و نتیجه‌ی کامپایل آن می‌شود:
var sum,
  slice = [].slice;

sum = function() {
  var nums, result;
  nums = 1 <= arguments.length ? slice.call(arguments, 0) : [];
  result = 0;
  nums.forEach(function(n) {
    return result += n;
  });
  return result;
};
 

فراخوانی توابع

برای فراخوانی توابع می‌توانید به مانند جاوااسکریپت از با پرانتز () یا ()apply و یا ()call صدا زده شوند. اگرچه مانند Ruby، کامپایلر CoffeeScript می‌توانند به صورت اتوماتیک توابعی با حداقل یک آرگومان را فراخوانی کند.

a = "Vahid!"

alert a
# برابر است با
alert(a)

alert inspect a
# برابر است با
alert(inspect(a))
اگرچه استفاده از پرانتز اختیاری است اما توصیه می‌شود در مواقعی که آرگومان‌های ارسالی بیش از یک مورد باشد توصیه می‌شود از پرانتز استفاده کنید.
در صورتی که تابعی بدون آرگومان باشد، برای فراخوانی آن بدون نوشتن پرانتز بعد از نام تابع، CoffeeScript نمی‌تواند تشخیص دهند که این یک تابع است و مانند یک متغیر با آن برخورد می‌کند. دراین رابطه، رفتار CoffeeScript بسیار شبیه به Python می‌باشد.
 

مطالب
MongoDB #13
توابع جمعی در MongoDB
عملگرهای جمعی، رکوردهای اطلاعات را پردازش می‌کنند و نتیجه‌های محاسبه شده را برمی‌گردانند. عملیات جمعی مقادیر چندین سند را باهم گروه بندی می‌کند و می‌تواند یک نوع از عملگرها را روی اطلاعات دسته بندی شده انجام دهد تا یک نتیجه‌ی واحد را برگرداند. در sql، دستور (*)count همراه Group by معادل یک تابع جمعی در MongoDB است.

متد ()aggregate
برای توابع جمعی در MongoDB باید از متد ()aggregate استفاده کنید.

گرامر
گرامر پایه متد ()aggregate به صورت زیر است:
>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

مثال
در این مجموعه، داده‌های زیر را دارید:
{
   _id: ObjectId(7df78ad8902c)
   title: 'MongoDB Overview', 
   description: 'MongoDB is no sql database',
   by_user: 'user1',
   url: 'http://www.site.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 100
},
{
   _id: ObjectId(7df78ad8902d)
   title: 'NoSQL Overview', 
   description: 'No sql database is very fast',
   by_user: 'user1',
   url: 'http://www.site.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 10
},
{
   _id: ObjectId(7df78ad8902e)
   title: 'Neo4j Overview', 
   description: 'Neo4j is no sql database',
   by_user: 'Neo4j',
   url: 'http://www.neo4j.com',
   tags: ['neo4j', 'database', 'NoSQL'],
   likes: 750
},
حالا اگر بخواهید از مجموعه‌ی بالا یک لیست را که تعداد دوره‌های نوشته شده توسط هر کاربر را نمایش می‌دهد، استخراج کنید، باید ار متد () aggregate به صورت زیر استفاده نمائید:
> db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])
{
   "result" : [
      {
         "_id" : "user1",
         "num_tutorial" : 2
      },
      {
         "_id" : "Neo4j",
         "num_tutorial" : 1
      }
   ],
   "ok" : 1
}
>

معادل کوئری بالا در sql بصورت زیر خواهد بود:
select by_user, count(*) from mycol group by by_user
در مثال بالا، سندهای گروه بندی شده‌ی توسط فیلد by_user را داریم و در هر اجرای by_user مقدار قبلی جمع کلی افزایش می‌یابد. در اینجا لیست عبارت‌های جمعی موجود، آمده است.
عبارت  توضیحات   مثال
 $sum  مقدار تعیین شده از همه سندهای مجموعه را جمع می‌کند.
 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
 $avg میانگین همه مقادیر بدست آمده از سندهای مجموعه را محاسبه می‌کند.
 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
 $min کمترین مقادیر مشابه را از همه سندهای مجموعه، بر می‌گرداند.
 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
 $max بیشترین مقادیر مشابه را از همه سندهای مجموعه، بر می‌گرداند.
 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
 $push یک مقدار را در سند نتیجه، در یک آرایه درج می‌کند.
 db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
 $addToSet یک مقدار را در سند نتیجه در یک آرایه درج می‌کند، اما مقدار تکراری ایجاد نمی‌کند.
 db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
 $first اولین سند از اسناد را برطبق گروه بندی بر می‌گرداند. معمولا این عبارت بعد از عبارت‌های مرتب سازی مرحله‌ای استفاده می‌شود.
 db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
 $last آخرین سند از اسناد را برطبق گروه بندی بر می‌گرداند. معمولا این عبارت بعد از عبارت‌های مرتب سازی مرحله‌ای استفاده می‌شود.
 db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

مفهوم Pipeline
در Command shell یونیکس، خط لوله (Pipeline) به معنی امکان اجرای یک عملیات روی چندین ورودی و استفاده از خروجی بعنوان ورودی برای دستور بعدی و ادامه‌ی آن است. MongoDB نیز این مفهوم را در چارچوب توابع جمعی پشتیبانی می‌کند. یک مجموعه از مراحل وجود دارند که هرکدام از آنها یک مجموعه از اسناد را بعنوان ورودی می‌گیرند و یک مجموعه از سند را بعنوان نتیجه (یا نتیجه را بعنوان سند JSON در پایان خط لوله) ارائه می‌دهند. این عمل به نوبه خود می‌تواند برای مرحله بعد و یا مراحل بعدی، استفاده شود.
مراحل ممکن در چارچوب توابع جمعی در زیر آمده اند:
  • $project : برای انتخاب چندین فیلد از یک مجموعه استفاده می‌شود.
  • $match : این یک عملگر فیلترگذاری است که می‌تواند میزان اسنادی را که بعنوان ورودی در مرحله بعد گرفته می‌شوند، کاهش دهد.
  • $group : این همان تابع جمعی است که در بالا توضیح داده شد.
  • $skip : توسط این عبارت، در یک لیست بدست آمده (نتیجه)، می‌توانید از لیست اسناد بصورت روبه جلو صرفنظر کنید. 
  • $limit : این عبارت تعداد اسناد را توسط عدد گرفته شده، از موقعیت فعلی برای نمایش محدود می‌کند. 
  • $unwind : این عبارت برای باز کردن (unwind) سندی که از آرایه‌ها بهره گیری می‌کند استفاده می‌شود. وقتی از آرایه استفاده می‌کنید، داده از نوع پیش پیوست (Pre-joined) است و با این نوع داده، این عمل برای داشتن سندهای اختصاصی نا تمام خواهد ماند. بنابراین با این مرحله می‌توانید میزان اسناد را برای مرحله بعد افزایش دهید. 
مطالب دوره‌ها
توابع(Function)
برنامه نویسی تابع گرا در یک جمله یعنی نوشتن توابع در پروژه و فراخوانی آن‌ها به همراه مقدار دهی به آرگومان‌های متناظر و دریافت خروجی در صورت نیاز. در #F پارامتر‌های یک تابع با پرانتز یا کاما از هم تمیز داده نمی‌شوند بلکه باید فقط از یک فضای خالی بین آن‌ها استفاده کنید.(البته می‌تونید برای خوانایی بهتر از پرانتز استفاده کنید)
let add x y = x + y
let result = add 4 5
printfn "(add 4 5) = %i" result
همان طور که می‌بینید تابعی به نام add داریم که دارای 2 پارامتر ورودی است به نام‌های x , y که فقط توسط یک فضای خالی از هم جدا شدند. حال به مثال دیگر توجه کنید.
let add x y = x + y
let result1 = add 4 5
let result2 = add 6 7
let finalResult = add result1 result2
در مثال بالا همان تابع add 2 بار فراخوانی شده است که یک بار مقدار خروجی تابع در یک شناسه به نام result1 و یک بار مقدار خروجی با مقادیر متفاوت در شناسه به نام result2 قرار گرفت. شناسه finalResult حاصل فراخوانی تابع add با مقادیر result1 , result2 است. می‌تونیم کد بالا رو به روش مناسب‌تری باز نویسی کنیم.
let add x y = x + y
let result =add (add 4 5) (add 6 7)
در اینجا برای خوانایی بهتر کد از پرانتز برای جداسازی مقدار پارامتر‌ها استفاده کردم.

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

let cylinderVolume radius length : float =
   
   let pi = 3.14159
   length * pi * radius * radius
در مثال بالا خروجی تابع مقدار ( length * pi * radius * radius ) است و نو ع آن float می‌باشد.
یک مثال دیگر:
let sign num =
    if num > 0 then "positive"
    elif num < 0 then "negative"
    else "zero"
خروجی تابع بالا از نوع string است و مقدار آن با توجه به ورودی تابع positive یا negative یا zero خواهد بود.

تعریف پارامترهای تابع با ذکر نوع به صورت صریح
اگر هنگام تعریف توابع مایل باشید که نوع پارامترها را به صورت صریح تعیین کنید از روش زیر استفاده می‌کنیم.
let replace(str: string) =
    str.Replace("A", "a")
تعریف تابع به همراه دو پارامتر و ذکر نوع فقط برای یکی از پارامتر‌ها :
let addu1 (x : uint32) y =
    x + y

Pipe-Forward Operator
در #F روشی دیگری برای تعریف توابع وجود دارد که به pipe-Forward معروف است. فقط کافیست از اپراتور (<|) به صورت زیر استفاده کنید.
let (|>) x f = f x
کد بالا به این معنی است که تابعی  یک پارامتر ورودی به نام x دارد و این پارامتر رو به تابع مورد نظر(هر تابعی که شما هنگام استفاده تعیین کنید) تحویل می‌دهد و خروجی را بر می‌گرداند. برای مثال
let result = 0.5 |> System.Math.Cos
یا
let add x y = x + y
let result = add 6 7 |> add 4 |> add 5
در مثال بالا ابتدا حاصل جمع 7 و 6 محاسبه می‌شود و نتیجه با 4 جمع می‌شود و دوباره نتیجه با 5 جمع می‌شود تا حاصل نهایی در result قرار گیرد. به نظر اکثر برنامه نویسان #F این روش نسبت به روش‌های قبلی خواناتر است. این روش همچنین مزایای دیگری نیز دارد که در مبحث Partial Function‌ها بحث خواهیم کرد.

Partial Fucntion Or Application
partial function به این معنی است که در هنگام فراخوانی یک تابع نیاز نیست که به تمام آرگومان‌های مورد نیاز مقدار اختصاص دهیم. برای نمونه در مثال بالا تابع add نیاز به 2 آرگومان ورودی داشت در حالی که فقط یک مقدار به آن پاس داده شد.
let result = add 6 7 |> add 4
دلیل برخورد #F با این مسئله این است که #F توابع رو به شکل مقدار در نظر می‌گیرد و اگر تمام مقادیر مورد نیاز یک تابع در هنگام فراخوانی تحویل داده نشود، از مقدار برگشت داده شده فراخوانی تابع قبلی استفاده خواهد کرد. البته این مورد همیشه خوشایند نیست. اما می‌تونیم با استفاده از پرانتز ر هنگام تعریف توابع مشخص کنیم که دقیقا نیاز به چند تا مقدار ورودی برای توابع داریم.
let sub (a, b) = a - b
let subFour = sub 4
کد بالا کامپایل نخواهد شد و خطای زیر رو مشاهده خواهید کرد.
prog.fs(15,19): error: FS0001: This expression has type
int
but is here used with type
'a * 'b
توابع بازگشتی
در مورد ماهیت توابع بازگشتی نیاز به توضیح نیست فقط در مورد نوع پیاده سازی اون در #F توضیح خواهم داد. برای تعریف توابع به صورت بازگشتی کافیست از کلمه rec بعد از let استفاده کنیم(زمانی که قصد فراخوانی تابع رو در خود تابع داشته باشیم). مثال پایین به خوبی مسئله را روشن خواهد کرد.(پیاده سازی تابع فیبو ناچی)
let rec fib x =
match x with
| 1 -> 1
| 2 -> 1
| x -> fib (x - 1) + fib (x - 2)
printfn "(fib 2) = %i" (fib 2) printfn "(fib 6) = %i" (fib 6) printfn "(fib 11) = %i" (fib 11) 
*درباره الگوی Matching در فصل بعد به صورت کامل توضیح خواهم داد.
خروجی برای مثال بالا به صورت خواهد شد.
(fib 2) = 1
(fib 6) = 8
(fib 11) = 89
توابع بازگشتی دو طرفه
گاهی اوقات توابع به صورت دوطرفه بازگشتی می‌شوند. یعنی فراخوانی توابع به صورت چرخشی انجام می‌شود. (فراخوانی یک تابع در تابع دیگر و بالعکس). به مثال زیر دقت کنید.
let rec Even x =
   if x = 0 then true 
   else Odd (x - 1)
and Odd x =
   if x = 1 then true 
   else Even (x - 1)
کاملا واضح است در تابع Even فراخوانی تابع Odd انجام می‌شود و در تابع Odd فراخوانی تابع Even. به این حالت mutual recursive می‌گویند.
ترکیب توابع
let firstFunction x = x + 1
let secondFunction x = x * 2
let newFunction = firstFunction >> secondFunction
let result = newFunction 100
در مثال بالا دو تابع به نام‌های firstFunction  و secondFunction داریم. بااستفاده از (<<) دو تابع را با هم ترکیب می‌کنیم. خروجی بدین صورت محاسبه می‌شود که ابتدا تابع firstFucntion مقدار x را محاسبه می‌کند و حاصل به تابع secondFucntion پاس داده می‌شود. در نهایت یک تابع جدید به نام newFunction خواهیم داشت که مقدار نهایی محاسبه خواهد شد. خروجی مثال بالا 202 است.

توابع تودرتو
در #F امکان تعریف توابع تودرتو وجود دارد. بعنی می‌تونیم یک تابع را در یک تابع دیگر تعریف کنیم. فقط نکته مهم در امر استفاده از توابع به این شکل این است که توابع تودرتو فقط در همون تابعی که تعریف می‌شوند قایل استفاده هستند و محدوده این توابع در خود همون تابع است.
let sumOfDivisors n =
    let rec loop current max acc =
//شروع تابع داخلی
 if current > max then acc else if n % current = 0 then loop (current + 1) max (acc + current) else loop (current + 1) max acc
//ادامه بدنه تابع اصلی
 let start = 2 let max = n / 2 (* largest factor, apart from n, cannot be > n / 2 *) let minSum = 1 + n (* 1 and n are already factors of n *) loop start max minSum printfn "%d" (sumOfDivisors 10)
در مثال بالا یک تابع تعریف کرده ایم به نام sumOfDivisors. در داخل این تابع یک تابع دیگر به نام loop داریم که از نوع بازگشتی است(به دلیل وجود rec بعد از let). بدنه تابع داخلی به صورت زیر است:
  if current > max then
            acc
        else
            if n % current = 0 then
                loop (current + 1) max (acc + current)
            else
                loop (current + 1) max acc
خروجی مثال بالا برای ورودی 10 عدد 18 می‌باشد. مجموع مقصوم علیه‌های عدد 10 (1 + 2 + 5 + 10 ).

آیا می‌توان توابع را Overload کرد؟
در #F امکان overloading برای یک تابع وجود ندارد. ولی متدها را می‌توان overload  کرد.(متد‌ها در فصل شی گرایی توضیح داده می‌شود).

do keyword
زمانی که قصد اجرای یک کد را بدون تعریف یک تابع داشته باشیم باید از do  استفاده کنیم. همچنین از do در انجام برخی عملیات پیش فرض در کلاس‌ها زیاد استفاده می‌کنیم.(در فصل شی گرایی با این مورد آشنا خواهید شد).
open System
open System.Windows.Forms

let form1 = new Form()
form1.Text <- "XYZ"

[<STAThread>]
do
   Application.Run(form1)

مطالب
ارسال انواع بی نام (Anonymous) بازگشتی توسط Entity framework به توابع خارجی
فرض کنید ساختار زیر را در مدل ساخته شده به وسیله‌ی Entity framework در پروژه‌ی خود داریم .



جدول Post با جدول GroupsDetail ارتباط یک به چند و در مقابل ان جدول GroupsDetail با جدول PostGroup ارتباط چند به یک دارد . به زبان ساده ما تعدادی گروه بندی برای  مطالب داریم (در جدول PostGroup) و میتوانیم برای هر مطلب تعدادی از گروه‌ها را در جدول GroupsDetail مشخص کنیم ...
فکر میکنم همین مقدار توضیح به اندازه‌ی کافی برای درک روابط مشخص شده در مدل رابطه ای مفروض کفایت کند.
حالا فرض کنید ما میخواهیم لیستی از عنوان مطالب موجود به همراه [نام] گروه‌های هر مطلب را داشته باشیم .
توجه شما رو به قطعه کد ساده‌ی زیر جلب میکنم:
var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new {
             id = pst.id, 
             Title = pst.Title, 
             GNames = pst.GroupsDetails.Select(grd => new { Name = grd.PostGroup.name }) 
             })
             .OrderByDescending(c => c.id)
             .ToList();
همانطور که شاهد هستید در قطعه کد بالا توسط خواص راهبری (Navigation Properties ) به صورت مستقیم نام گروه‌های ثبت شده برای هر مطلب در جدول GroupsDetail را از جدول  PostGroup استخراج کردیم . خب تا اینجا مسئله ای وجود نداشت ( البته با ORM‌ها این کار بسیار سهل و اسان شده است تا جایی که در حالت عادی برای همین کار باید کلی join نویسی کرد و .... در کل خدارو شکر که از دست کارهای تکراری و خسته کننده توسط این موجودات مهربان (ORM's) نجات یافتیم )...

خب میرسیم به ادامه‌ی مطلبمون . نتیجه‌ی این query چه خواهد بود ؟ کاملا واضح است که ما تعداد دلخواهی از فیلد‌ها رو برای واکشی مشخص کردیم پس در نتیجه نوع داده ای که توسط این query بازگشت داده خواهد شد یک لیست از یک نوع بی نام میباشد ... چگونه از نتیجه‌ی بازگشتی این query در صورت ارسال اون به عنوان یک پارامتر به یک تابع استفاده کنیم ؟ اگر ما تمامی فیلد‌های جدول Post را واکشی میکریم مقدار بازگشتی یک لیست از نوع Post بود که به راحتی قابل استفاده بود مثل مثال زیر

public bool myfunc(List<Post> query)
        {
            foreach (var item in query)
            {
            .....  string title =  item.Title;
            }
...return true;
        }
.
.
.

var context = new Models.EntitiesConnection();
var queryx = context.Posts.ToList();
.... myfunc(queryx );
اما حالا با یک لیست از یک نوع بی نام  رو به رو هستیم ... چند راه مختلف برای دسترسی به این گونه از مقادیر بازگشتی داریم .
1 : استفاده از Reflection برای دسترسی به فیلدهای مشخص شده .
2 : تعریف یک مدل کامل بر اساس فیلدهای مشخص شده‌ی بازگشتی و ارسال یک لیست از نوع تعریف شده به تابع . ( به نظرم این روش خسته کنندست و زیاد شکیل نیست چون برای هر query خاص مجبوریم یک مدل کلی تعریف کنیم و این باعث میشه تعداد زیادی مدل در نهایت داشته باشیم که استفاده‌ی زیادی از اونها نمیشه عملا )
3 : استفاده از یکی از روش‌های خلاقانه‌ی تبدیل نوع Anonymous‌ها .

روش سوم روش مورد علاقه‌ی من هست و انعطاف بالایی داره و خیلی هم شیکو مجلسیه ! در ضمن این روش که قراره ذکر بشه کاربردهای فراوانی داره که این مورد فقط یکی از اونهاست
خب بریم سر اصل مطلب . به کد زیر توجه کنید :

        public static List<T> CreateGenericListFromAnonymous<T>(object obj, T example)
        {
            return (List<T>)obj;
        }
        public static IEnumerable<T> CreateEmptyAnonymousIEnumerable<T>(T example)
        {
            return new List<T>(); ;
        }

////

        public bool myfunc(object query)
        {
            var cquery = CreateGenericListFromAnonymous(query, 
                              new { 
                                       id = 0, 
                                       Title = string.Empty, 
                                       GNames = CreateEmptyAnonymousIEnumerable(new { Name = string.Empty }) 
                              });
            foreach (var item in cquery)
            {
                string title = item.Title;
                foreach (var gname in item.GNames)
                {
                    string gn = gname.Name;
                }
.
.
.
            }
            return false;
        }

.
.
.
var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new
                              { 
                                   id = pst.id, 
                                   Title = pst.Title,
                                   GNames = pst.GroupsDetails.Select(grd => new { Name = grd.PostGroup.name }) 
                             }).OrderByDescending(c => c.id);
            myfunc(query.ToList());
در کد بالا به صورت تو در تو از انواع بی نام استفاده شده تا مطلب براتون خوب جا بیوفته . یعنی یک نوع بی نام که یکی از فیلدهای اون یک لیست از یک نوع بی نام دیگست ... خب میبینید که خیلی راحت با دو تابع ما تبدیل انواع بی نام رو به صورت inline انجام دادیم و ازش استفاده کردیم . بدون اینکه نیاز باشه ما یک مدل مجزا ایجاد کنیم ...
تابع CreateGenericListFromAnonymous : یک object رو میگیره و اون رو به یک لیست تبدیل میکنه بر اساس نوعی که به صورت inline براش مشخص میکنیم .
تابع CreateEmptyAnonymousIEnumerable یک لیست از نوع IEnumerable رو بر اساس نوعی که به صورت inline براش مشخص کردیم بر میگردونه . دلیل اینکه من در اینجا این تابع رو نوشتم این بود که ما در query یک فیلد با نام GNames داشتیم که مجموعه ای از نام گروه‌های هر مطلب بود که از نوع IEnumerable هستش . در واقع ما در اینجا نوع بی نامی داریم که یکی از فیلدهای اون یک لیست از یک نوع بی نام دیگست .  امیدوارم مطلب براتون جا افتاده باشه.
دوستان عزیز سوالی بود در قسمت نظرات مطرح کنید.  
مطالب
تعدادی از توابع kendo UI Treeview
kendo ui یکی از جذاب‌ترین و بهترین فریم ورک‌های HTML5 است که استفاده از آن بین برنامه نویسان جا افتاده است و تلریک هم پشتیبانی خوبی از آن به عمل آورده است. من هم به تازگی از شیء treeview آن  استفاده کردم و موقعیکه کارم با شیء Treeview به پایان رسید، یک فایل کوچک جاوااسکریپت به کار اضافه شد که شامل تعدادی از توابع چون حذف گره و ... بود که تصمیم گرفتم بر اساس مستندات و نیازهای عمومی، تعداد این توابع را بالا ببرم که برای استفاده در صفحات و پروژه‌های دیگر و بالا بردن امتیاز استفاده مجدد از کد از آن استفاده کنم. سورس آن در گیت هاب موجود است.
بعد از صدا زدن فایل‌های مورد نیاز کندو، این فایل جاوااسکریپتی را هم به پروژه اضافه کنید. بیشتر توابع تست شده و جواب گرفته و فکر نکنم مشکلی باشد؛ هر چند عیب یابی آن هم ساده است و مشکلی برای برطرف کردن آن وجود ندارد و هر تابع دو یا سه خط بیشتر نیست.

معرفی توابع


    var treeview;

    function FindTreeViewObj(objName) {
        treeview = $(objName).data("kendoTreeView");
    }
اولین و مهمترین تابع می‌باشد که باید قبل از همه در زمان لود پروژه بعد از ایجاد درخت آن را صدا بزنید، در صورتی که صدا زده نشود، بقیه‌ی توابع کار نخواهند کرد. این تابع وظیفه دارد شیء درخت معرفی شده را پیدا کرده و داخل یک متغیر عمومی به اسم treeview قرار دهد:
FindTreeViewObj("#treeview");

    function GetSelectedNode() {
        return treeview.select();
    }
گره انتخابی را باز می‌گرداند:
GetSelectedNode();


    function DisableSelectedNode() {
        treeview.enable(GetSelectedNode(), false);
    }
گره انتخابی را غیرفعال می‌کند:
DisableSelectedNode();

    function EnableSelectedNode() {
        treeview.enable(GetSelectedNode(), true);
    }
گره انتخابی را فعال می‌کند:
EnableSelectedNode();

    function EnableAllNodes() {
        treeview.enable(".k-item");
    }
همه‌ی گره‌ها را فعال می‌کند:
EnableAllNodes();

    function ExpandAllNodes() {
        treeview.expand(".k-item");
    }
همه‌ی گره‌ها را به سمت فرزندان باز می‌کند:
ExpandAllNodes();

    function CollapseAllNodes() {
        treeview.collapse(".k-item");
    }
همه گره‌ها به سمت والد را جمع می‌کند:
CollapseAllNodes();

    function RemoveSelectedNode() {
        treeview.remove(GetSelectedNode());
    }
گره انتخابی را حذف می‌کند:
RemoveSelectedNode()

    function FilterTreeView(filterText) {
        if (filterText !== "") {
            treeview.dataSource.filter({
                field: "text",
                operator: "contains",
                value: filterText
            });
        } else {
            treeview.dataSource.filter({});
        }
    }
گره‌هایی که عبارت مد نظر در آن‌ها باشند، نمایش میابند:
FilterTreeView('my node')

    function SortTreeView(sortType) {
        treeview.dataSource.sort({
            field: "text",
            dir: sortType
        });
    }
گره‌ها را به صورت صعودی یا نزولی مرتب می‌کند. مقادیر پارامتر ورودی آن یا "asc" است یا "desc"
SortTreeView('asc');
SortTreeView('desc');

    function GetSelectedDataItem() {
        return treeview.dataItem(GetSelectedNode());
    }
قسمت اطلاعاتی یک گره که شامل مواردی چون عنوان یا Id است را باز می‌گرداند:
GetSelectedDataItem();

    function GetSelectedNodeId() {
        var data = GetSelectedDataItem();
        return data.id;
    }
Id گره را بر میگرداند:
GetSelectedNodeId();

    function GetSelectedNodeText() {
        var data = GetSelectedDataItem();
        return data.Name;
    }
متن یا مقدار گره را باز میگرداند:
GetSelectedNodeText();

    function SetSelectedNodeText(value) {
        var node = GetSelectedNode();
        treeview.text(node, value);
    }
مقدار گره را به مقدار جدید تغییر می‌دهد:
SetSelectedNodeText('new value');

    function GetNodeByText(text) {
        return treeview.findByText(text);
    }
اولین گره با این متن را باز میگرداند:
GetNodeByText('mynode');

    function GetNodeByText(id) {
        return treeview.findByUid(id);
    }
گره ای با این Id را باز میگرداند:
GetNodeByText(4);

    function InsertAfter(item, nextItem) {
        treeview.insertAfter({ text: "item" }, GetNodeByText(nextItem));
    }
متن دو گره را دریافت می‌کند، گره‌ای را بر اساس متن پارامتر دوم پیدا کرده و بعد از آن گره اول را با مقدار پارامتر اول درج می‌کند.
InsertAfter('new item', 'old item')

    function MoveToAfter(firstItem, secondItem) {
        treeview.insertAfter(GetNodeByText(firstItem), GetNodeByText(secondItem));
    }
مقدار گره‌ها را دریافت می‌کند و بعد از پیدا کردن آن‌ها، گره اول را به بعد از گره دوم انتقال می‌دهد:
MoveToAfter('firstItem', 'secondItem');

    function InsertBefore(item, nextItem) {
        treeview.insertBefore({ text: "item" }, GetNodeByText(nextItem));
    }

    function MoveToBefore(firstItem, secondItem) {
        treeview.insertBefore(GetNodeByText(firstItem), GetNodeByText(secondItem));
    }
دو تابع بعد مثل توابع بالا هستند و فقط به قبل آن اضافه می‌کند یا انتقال می‌دهد.
InsertBefore('new item', 'old item');
MoveToBefore('firstItem', 'secondItem');

    function GetParent(node) {
        return treeview.parent(node);
    }
والد گره انتخابی را بر میگرداند:
GetParent(GetSelectedNode());

    function Toggle(node) {
        treeview.toggle(node);
    }
گره را (در صورت داشتن فرزند)  باز و بسته می‌کند:
Toggle(GetSelectedNode());

    function NewNode(nodeText, nodeValue, selectedNode) {
        treeview.append({
            Name: nodeText,
            Id: nodeValue
        }, selectedNode);
    }
گره جدیدی را اضافه می‌کند. پارامتر اول مقدار گره، پارامتر دوم Id گره و پارامتر سوم در صورتی که بخواهید یک زیر گره باشد، باید گره والد را به آن پاس کنید و درصورتی که زیر گره نیست آن را نال مقدار دهی کنید.
NewNode('new node', 1, null);
NewNode('new sub node', 2, GetSelectedNode());
مطالب
آشنایی بیشتر با AngularJS Directive

در مطلب آشنایی با Directive‌ها در AngularJS با نحوه‌ی ایجاد Directive آشنا شدیم. هدف از این مطلب، آشنایی بیشتر با Directive در AngularJS است؛ یکی از بهترین فریم ورک‌های جاوااسکریپتی، با قابلیت ایجاد کتابخانه‌هایی از کامپوننت‌ها که می‌توانند به HTML اضافه شوند .

کتابخانه‌های جاوااسکریپتی زیادی وجود دارند. به عنوان مثال Bootstrap یکی از محبوب‌ترین "front-end framework" ها است که امکان تغییر در ظاهر المنت‌ها را فراهم می‌کند و شامل تعدادی کامپوننت جاوااسکریپتی نیز می‌باشد. مشکل کار، در هنگام استفاده از کامپوننت ها است. شخصی که در حال توسعه‌ی HTML است باید در کد جاوااسکریپتی خود از  jQuery استفاده کند و بعنوان مثال یک Popover  را فعال یا غیر فعال کند و این، یک فرآیند خسته کننده و مستعد خطا است. 


یک مثال ساده از Directives AngularJS و بررسی آن

var m = angular.module("myApp");
 
myApp.directive("myDir", function() {
  return {
  restrict: "E",   
  scope: {     
   name: "@",   
   amount: "=",  
   save: "&"    
  },
  template:    
   "<div>" +
   "  {{name}}: <input ng-model='amount' />" +
   "  <button ng-click='save()'>Save</button>" +
   "</div>",
  replace: true,   
  transclude: false, 
  controller: [ "$scope", function ($scope) { …  }],
  link: function (scope, element, attrs, controller) {…}
  }
});

به الگوی نامگذاری directive دقت کنید. پیشوند my شبیه به یک namespace است. بنابراین اگر یک Application از دایرکتیوهای قرار گرفته در Module ‌های متفاوت استفاده کند، به راحتی می‌توان محل تعریف یک directive را تشخیص داد. این نام می‌تواند نشان دهنده‌ی این باشد که این directive را خودتان توسعه داده‌اید یا از یک directive توسعه داده شده توسط شخص دیگری در حال استفاده هستید. به هر حال این نحوه‌ی نام گذاری یک اجبار نیست و به عنوان یک پیشنهاد است.

سازنده directive یک شیء را با تعدادی خاصیت باز می‌گرداند که تمامی آنها در سایت AngularJS توضیح داده شده‌اند. در اینجا قصد داریم تا توضیحی مختصر در مورد کاری که این خصوصیات انجام می‌دهند داشته باشیم.

· restrict : تشخیص می‌دهد که آیا directive در HTML استفاده خواهد شد. گزینه‌های قابل استفاده ‘A’ ،  ‘E’ ، ‘C’ برای attribute ، element ، class و یا comment است . پیش فرض ‘A’ برای attribute است. اما ما بیشتر علاقه به استفاده از ویژگی element برای ایجاد المنت‌های UI داریم.

· scope : ایجاد یک scope ایزوله که متعلق به directive است و موجب ایزوله شدن آن از scope صدا زننده directive می‌شود. متغیرهای scope پدر از طریق خصوصیات تگ directive ارسال می‌شوند. این ایزوله کردن زمانی کاربردی است که در حال ایجاد کامپوننت هایی با قابلیت استفاده مجدد هستیم، که نباید متکی به scope پدر باشند. شیء scope در directive نام و نوع متغیرهای scope را تعیین می‌کنند. در مثال بالا سه متغیر برای scope تعریف شده است:

-   name: "@" (by value, one-way) : علامت @ مشخص می‌کند که مقدار متغیر ارسال می‌شود. Directive یک رشته را دریافت می‌کند که شامل مقدار ارسال شده از scope پدر می‌باشد. Directive می‌تواند از آن استفاده کند، اما نمی‌تواند مقدار آن را در scope پدر تغییر دهد.

-   amount: "=" (by reference, two-way) : علامت = مشخص می‌کند این متغیر با ارجاع ارسال می‌شود. Directive یک ارجاع به مقدار متغیر در scope اصلی دریافت می‌کند. مقدار می‌تواند هر نوع داده ای، شامل یک شیء complex یا یک آرایه باشد. Directive می‌تواند مقدار را در scope پدر تغییر دهد. این نوع متغیر، زمانیکه نیاز باشد directive مقدار را در scope پدر تغییر دهد، استفاده می‌شود.

-   save: "&" (expression) : علامت & مشخص می‌کند این متغیر یک expression را که در scope پدر اجرا می‌شود، نگهداری می‌کند. اکنون directive قابلیت انجام کارهایی فراتر از تغییر یک مقدار را دارد. به عنوان مثال می‌توان یک تابع را از scope پدر فراخوانی و نتیجه‌ی اجرا را دریافت کرد.

· template :  الگوی رشته ای که جایگزین المنت تعریف شده می‌شود. فرآیند جایگزینی تمامی خصوصیات را از المنت قدیمی به المنت جدید انتقال می‌دهد. به نحوه استفاده از متغیر‌های تعریف شده در scope ایزوله دقت کنید. این مورد به شما امکان تعریف directive های macro-style را می‌دهد که نیاز به کد اضافه‌ای، ندارند. اگرچه در بیشتر موارد الگو  یک تگ ساده <div> است که از کد‌های link که در زیر توضیح داده شده است استفاده می‌کند.

· replace : تعیین می‌کند که آیا الگوی directive باید جایگزین المنت شود. مقدار پیش فرض false است.

· transclude : تعیین کننده این است که محتوای directive باید در المنت کپی شود یا خیر. در مثال زیر المنت tab شامل المنت‌های HTML دیگر است پس transclude برابر true است.  

<body ng-app="components">
  <h3>BootStrap Tab Component</h3>
    <tabs>
       <pane title="First Tab">
          <div>This is the content of the first tab.</div>
       </pane>
       <pane title="Second Tab">
          <div>This is the content of the second tab.</div>
       </pane>
    </tabs>
</body>
 

· link : این تابع بیشتر منطق directive را شامل می‌شود. Link وظیفه دستکاری DOM ، ایجاد event listener ‌ها و... را دارد. تابع Link پارامترهای زیر را دریافت می‌کند:

-   scope : ارجاع به scope ایزوله شده directive دارد.

-   element : ارجاع به المنت‌های DOM که directive را تعریف کرده اند. تابع link معمولا برای دستکاری المنت از jQuery استفاده می‌کند. (یا از Angular's jqLite در صورتی که jQuery بارگذاری نشده باشد)

-   controller : در مواقعی که از دایرکتیو‌های تو در تو استفاده می‌شود کاربرد دارد. این پارامتر یک directive فرزند با ارجاعی به پدر را فراهم می‌کند، بنابراین موجب ارتباط  directive ‌ها می‌شود.

به عنوان مثال،  این directive  که پیاده سازی bootstrap tab را انجام داده است، می‌توانید مشاهده نمایید.

موفق باشید

مطالب
PowerShell 7.x - قسمت پنجم - اسکریپت بلاک و توابع
همانطور که در قسمت قبل اشاره شد، توابع نیز یکی از ویژگی‌های اصلی PowerShell هستند. قبل از بررسی بیشتر توابع بهتر است ابتدا با مفهوم script block آشنا شویم. script blocks به مجموعه‌ایی از دستورات گفته میشود که داخل یک بلاک قرار میگیرند. در واقع هر چیزی داخل {} یک script block محسوب میشود (البته به جز hash tables). به عنوان مثال در کد زیر از یک script block مخصوص، با نام فیلتر استفاده شده است که یک ورودی برای پارامتر FilterScript مربوط به دستور Where-Object میباشد. چیزی که این script block را متمایز میکند، خروجی آن است. به این معنا که خروجی آن باید یک مقدار بولین باشد: 
Get-Process | Where-Object { $_.Name -eq 'Dropbox' }
script blocks را به صورت مستقیم درون command line هم میتوانیم استفاده کنیم. به محض تایپ کردن } و زدن کلید enter، امکان نوشتن اسکریپت‌های چندخطی را درون ترمینال خواهیم داشت. در نهایت با بستن script block و زدن کلید enter، از بلاک خارج خواهیم شد: 
PS /Users/sirwanafifi/Desktop> $block = {
>> $newVar = 10
>> Write-Host $newVar
>> }
با اینکار یک بلاک از کد را داخل متغیری با اسم block ذخیره کرده‌ایم. برای فراخوانی این قطعه کد میتوانیم از یک عملگر مخصوص با نام invocation operator یا call operator استفاده کنیم: 
PS /Users/sirwanafifi/Desktop> & $block
یا حتی میتوانیم از Invoke-Command نیز برای اجرای بلاک استفاده کنیم. همچنین از عملگر & برای فراخوانی یک expression رشته‌ایی نیز میتوان استفاده کرد: 
PS /Users/sirwanafifi/Desktop> & "Get-Process"
البته این نکته را در نظر داشته باشید که & قادر به پارز کردن (parse) یک expression نیست. به عنوان مثال اجرای کد زیر با خطا مواجه خواهد شد (برای حل این مشکل میتوانید بجای آن از Invoke-Expression استفاده کنید که امکان پارز کردن پارامترها را نیز دارد):
PS /Users/sirwanafifi/Desktop> & "1 + 1"
or
PS /Users/sirwanafifi/Desktop> & "Get-Process -Name Slack"

توابع
در قسمت قبل با نحوه ایجاد توابع آشنا شدیم. به این نوع توابع، basic functions گفته میشود و ساده‌ترین نوع توابع در PowerShell هستند. همچنین خیلی محدود نیز میباشند؛ یکسری ورودی/خروجی دارند. برای کنترل بیشتر روی نحوه فراخوانی توابع (به عنوان مثال دریافت ورودی از pipeline و…) باید از advanced functions یا توابع پیشرفته استفاده کنیم. در واقع به محض استفاده از اتریبیوتی با نام [()CmdletBinding] تابع ما تبدیل به یک advanced function خواهد شد. منظور از دریافت ورودی از pipeline این است که بتوانیم خروجی دستورات را به تابع‌مان pipe کنیم اینکار در basic function امکانپذیر نیست: 
Function Add-Something {
    Write-Host "$_ World"
}

"Hello" | Add-Something
اما با کمک advanced functions میتوانیم چنین قابلیتی را داشته باشیم: 
Function Add-Something {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$Name
    )

    Write-Host "$Name World"
}

"Hello" | Add-Something
یکی دیگر از ویژگی‌های advanced functions امکان استفاده فلگ Verbose حین فراخوانی دستورات میباشد. به عنوان مثال قطعه کد زیر را در نظر بگیرید: 
$API_KEY = "...."

Function Read-WeatherData {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$CityName
    )

    $Url = "https://api.openweathermap.org/data/2.5/forecast?q=$CityName&cnt=40&appid=$API_KEY&units=metric"
    Try {
        Write-Verbose "Reading weather data for $CityName"
        $Response = Invoke-RestMethod -Uri $Url
        $Response.list | ForEach-Object {
            Write-Verbose "Processing $($_.dt_txt)"
            [PSCustomObject]@{
                City               = $Response.city.name
                DateTime           = [DateTime]::Parse($_.dt_txt)
                Temperature        = $_.main.temp
                Humidity           = $_.main.humidity
                Pressure           = $_.main.pressure
                WindSpeed          = $_.wind.speed
                WindDirection      = $_.wind.deg
                Cloudiness         = $_.clouds.all
                Weather            = $_.weather.main
                WeatherDescription = $_.weather.description
            }
        } | Where-Object { $_.DateTime.Date -eq (Get-Date).Date }
        Write-Verbose "Done processing $CityName"
    }
    Catch {
        Write-Error $_.Exception.Message
    }
}
کاری که تابع فوق انجام میدهد، دریافت دیتای پیش‌بینی وضعیت آب‌وهوای یک شهر است. در حالت عادی فراخوانی تابع فوق پیام‌های Verbose را نمایش نمیدهد. از آنجائیکه تابع فوق یک advanced function است، میتوانیم فلگ Verbose را نیز وارد کنیم. با اینکار به صورت صریح گفته‌ایم که پیام‌های از نوع Verbose را نیز نمایش دهد: 
Read-WeatherData -CityName "London" -Verbose
هر چند این مقدار را همانطور که در قسمت‌های قبلی عنوان شد میتوانیم تغییر دهیم که دیگر مجبور نباشیم با فراخوانی هر تابع، این فلگ را نیز ارسال کنیم. بیشتر دستورات native نیز قابلیت نمایش پیام‌های Verbose را با ارسال همین فلگ در اختیارمان قرار میدهند. بنابراین بهتر است برای امکان مشاهده جزئیات بیشتر حین فراخوانی توابع‌مان از Write-Verbose استفاده کنیم. در ادامه اجزای دیگر توابع را بررسی خواهیم کرد (بیشتر این اجزا درون یک script block نیز قابل استفاده هستند)

کنترل کامل بر روی ورودی‌های توابع
بر روی ورودی‌های یک تابع میتوانیم کنترل نسبتاً کاملی داشتیم باشیم. PowerShell یک مجموعه وسیع از قابلیت‌ها را برای هندل کردن پارامترها و همچنین اعتبارسنجی ورودی‌ها ارائه میدهد. به عنوان مثال میتوانیم یک پارامتر را mandatory کنیم یا اینکه امکان positional binding و غیره را تعیین کنیم. اتریبیوت Parameter در واقع یک وهله از System.Management.Automation.ParameterAttribute میباشد. میتوانید با نوشتن دستور زیر لیستی از خواصی را که میتوانید همراه با این اتریبیوت تعیین کنید، مشاهده کنید: 
PS /> [Parameter]::new()

ExperimentName                  :
ExperimentAction                : None
Position                        : -2147483648
ParameterSetName                : __AllParameterSets
Mandatory                       : False
ValueFromPipeline               : False
ValueFromPipelineByPropertyName : False
ValueFromRemainingArguments     : False
HelpMessage                     :
HelpMessageBaseName             :
HelpMessageResourceId           :
DontShow                        : False
TypeId                          : System.Management.Automation.ParameterAttribute
در ادامه یک مثال از نحوه هندل کردن ورودی‌های یک تابع را بررسی خواهیم کرد. تابع زیر یک لیست از URLها را از کاربر دریافت کرده و یک health check توسط دستور Test-Connection انجام میدهد. در کد زیر پارامتر Websites را با تعدادی اتریبیوت مزین کرده‌ایم. توسط اتریبیوت Parameter تعیین کرده‌ایم که ورودی الزامی است و همچنین مقدار آن میتواند از pipeline نیز دریافت شود. در ادامه توسط ValidatePattern یک عبارت باقاعده را برای بررسی صحیح بودن URL دریافتی نوشته‌ایم. از آنجائیکه ورودی از نوع آرایه‌ایی از string تعریف شده است، این تست برای هر آیتم از آرایه بررسی خواهد شد. برای پارامتر دوم یعنی Count نیز رنج مقداری را که کاربر وارد میکند، حداقل ۳ و حداکثر ۳ انتخاب کرده‌ایم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidatePattern('^www\..*')]
        [string[]]$Websites,
        [ValidateRange(1, 3)]
        [int]$Count = 3
    )
    $Results = @()
    $Websites | ForEach-Object {
        $Website = $_
        $Result = Test-Connection -ComputerName $Website -Count $Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Results += @{
            Website = $Website
            Result  = $ResultText
        }
        Write-Verbose "The result of pinging $Website is $ResultText"
    }
    $Results | ForEach-Object { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $Count }; } 
    }
}
یکی دیگر از اعتبارسنجی‌هایی که میتوانیم برای پارامترهای یک تابع انتخاب کنیم، ValidateScript است. توسط این اتریبیوت میتوانیم یک منطق سفارشی برای اعتبارسنجی مقادیر پارامترها بنویسیم. به عنوان مثال تابع فوق را به گونه‌ایی تغییر خواهیم داد که لیست وب‌سایت‌ها را از طریق یک فایل JSON دریافت کند. میخواهیم قبل از دریافت فایل مطمئن شویم که فایل، به صورت فیزیکی روی دیسک وجود دارد، در غیراینصورت باید یک خطا را به کاربر نمایش دهیم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                If (-Not ($_ | Test-Path) ) {
                    Throw "File or folder does not exist" 
                }
                If (-Not ($_ | Test-Path -PathType Leaf) ) {
                    Throw "The Path argument must be a file. Folder paths are not allowed."
                }
                If ($_ -NotMatch "(\.json)$") {
                    throw "The file specified in the path argument must be either of type json"
                }
                Return $true
            })]
        [Alias("src", "source", "file")]
        [System.IO.FileInfo]$Path,
        [int]$Count = 1
    )
    $Results = [System.Collections.ArrayList]@()
    $Urls = Get-Content -Path $Path | ConvertFrom-Json
    $Urls | ForEach-Object -Parallel {
        $Website = $_.url
        $Result = Test-Connection -ComputerName $Website -Count $using:Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Item = @{
            Website = $Website
            Result  = $ResultText
        }
        $null = ($using:Results).Add($Item)
    }
    
    $Results | ForEach-Object -Parallel { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $using:Count }; } 
    }
}
تابع Ping-Website را جهت بررسی فیچر جدیدی که همراه با دستور ForEach-Object استفاده میشود، تغییر داده‌ایم تا به صورت Parallel عمل کند؛ این قابلیت از نسخه ۷ به بعد به PowerShell اضافه شده است. از آنجائیکه این قابلیت باعث میشود script block مربوط به ForEach-Object درون یک context دیگر با نام runspace اجرا شود. در نتیجه برای دسترسی به متغیرهای بیرون از script block نیاز خواهیم داشت از یک متغیر خودکار تحت‌عنوان using قبل از نام متغیر و بعد از علامت $ استفاده کنیم. همچنین آرایه مثال قبل را نیز به ArrayList تغییر داده‌ایم. زیرا در حالت قبلی امکان تغییر سایز یک آرایه با سایز ثابت را نخواهیم داشت. نکته دیگری که در مورد کد فوق میتوان به آن توجه کرد، نال کردن خروجی متد Add مربوط به آرایه‌ی Results است. همانطور که در قسمت قبل توضیح دادیم، از این تکنیک برای suppress کردن خروجی استفاده میکنیم و چون در اینجا خروجی متد Add یک عدد میباشد، با تکنیک فوق، خروجی را دیگر درون کنسول مشاهده نخواهیم کرد. توسط اتریبیوت Alias نیز نام‌های دیگری را که میتوان برای پارامتر Path حین فراخوانی تابع استفاده کرد، تعیین کرده‌ایم. لیست کامل اتریبیوت‌هایی را که میتوان برای پارامترهای یک تابع تعیین کرد، میتوانید در مستندات PowerShell ببینید. 
نکته: اگر تابع فوق را همراه با فلگ Verbose فراخوانی کنیم، لاگ‌های موردنظر را درون کنسول مشاهده نخواهیم کرد؛ زیرا همانطور که اشاره شد script block درون یک context جدا اجرا میشود و باید متغیرهای خودکار مربوط به Output را مجدداً مقداردهی کنیم:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    # As before
    $Urls | ForEach-Object -Parallel {
        $DebugPreference = $using:DebugPreference 
        $VerbosePreference = $using:VerbosePreference 
        $InformationPreference = $using:InformationPreference 
        
        # As before
    }
    
    # As before
}

قابلیت تعریف بلاک‌ها/توابع، به صورت تودرتو  
درون توابع و script block امکان نوشتن بلاک‌های تودرتو را نیز داریم:
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }
    $someVariable = 20

    & $doSomeWork
}
خروجی بلاک فوق  Some variable value: 20 خواهد بود؛ زیرا قبل از فراخوانی doSomeWork مقدار متغیر عددی someVariable را به ۲۰ تغییر داده‌ایم. برای script blocks این امکان را داریم که دقیقاً در همان جایی که بلاک را تعریف میکنیم، یک snapshot تهیه کنیم. در اینحالت خروجی، مقدار Some variable value: 10 خواهد شد: 
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }.GetNewClosure()
    $someVariable = 20

    & $doSomeWork
}
یکسری بلاک‌های ویژه نیز درون توابع و script blockها میتوانیم بنویسیم که اصطلاحاً به name blocks معروف هستند:
begin
process
end
dynamicparam
درون یک تابع اگر هیچکدام از بلاک‌های فوق استفاده نشود، به صورت پیش‌فرض بدنه تابع، درون بلاک end قرار خواهد گرفت. بلاک begin قبل از شروع pipeline اجرا میشود. process به ازای هر آیتم pipe شده اجرا خواهد شد. end نیز در پایان اجرا میشود. به عنوان مثال تابع زیر را در نظر بگیرید:
function Show-Pipeline {
    begin { 
        Write-Host "Pipeline start" 
    }
    process { 
        Write-Host  "Pipeline process $_" 
    }
    end { 
        Write-Host  "Pipeline end $_" 
    }
}
در ادامه یکسری آیتم را به ورودی این تابع pipe خواهیم کرد:
PS /> 1..2 | Show-Pipeline                                   
Pipeline start 
Pipeline process 1
Pipeline process 2
Pipeline end 2
همانطور که مشاهده میکنید، به ازای هر آیتم pipe شده، یکبار بلاک process اجرا شده است. همچنین برای دسترسی به مقدار آیتم pipe شده نیز از متغیر خودکار _$ استفاده کرده‌ایم (PSItem$ نیز به همین متغیر اشاره دارد).

با توجه به توضیحات named blockهای فوق، اکنون اگر بخواهیم نسخه اول تابع Ping-Website را با pipe کردن یک آرایه فراخوانی کنیم، خروجی که در کنسول نمایش داده خواهد شد، تنها آیتم آخر از آرایه خواهد بود:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website                 

Website       Result  Number Of Attempts
-------       ------  ------------------
www.yahoo.com Success                  3
دلیل آن نیز این است که به صورت صریح کدها را درون بلاک process ننوشته بودیم. همانطور که عنوان شد، در حالت پیش‌فرض، بدنه توابع درون بلاک end قرار خواهند گرفت و تنها یکبار اجرا خواهند شد. بنابراین:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    process {
        # As before
    }
}
اینبار اگر تابع را مجدداً فراخوانی کنیم، خروجی مطلوب را نمایش خواهد داد:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website

Website        Result  Number Of Attempts
-------        ------  ------------------
www.google.com Success                  3
www.yahoo.com  Success                  3

بلاک dynamicparam
از این بلاک برای تعریف پارامترهای داینامیک که به صورت on the fly نیاز هست ایجاد شوند، استفاده میشود. برای درک بهتر آن فرض کنید میخواهیم تابعی را بنویسیم که امکان خواندن یک فایل CSV را به ما میدهد. تا اینجای کار توسط Import-CSV به یک خط دستور قابل انجام است. اما فرض کنید میخواهیم به کاربر این امکان را بدهیم که یک ستون موردنظر از فایل را مشاهده کند. همچنین میخواهیم یک اعتبارسنجی هم روی نام ستونی که کاربر قرار است وارد کند نیز داشته باشیم. به عنوان مثال یک فایل CSV با ستون‌های name, lname, age داریم و کاربر میخواهد تنها ستون اول یک name را واکشی کند:
PS /> Read-Csv ./users.csv -Columns name
برای اینکار میتوانیم با کمک dynamic param یک پارامتر را در زمان اجرا ایجاده کرده و مقادیری را که کاربر برای ستون‌ها مجاز است وارد کند، براساس هدر فایل CSV تنظیم کنیم:
using namespace System.Management.Automation
Function Read-Csv {
    Param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path
    )
    DynamicParam {
        $firstLine = Get-Content $Path | Select-Object -First 1
        [String[]]$headers = $firstLine -split ', '
        $parameters = [RuntimeDefinedParameterDictionary]::new()
        $parameter = [RuntimeDefinedParameter]::new(
            'Columns', [String[]], [Attribute[]]@(
                [Parameter]@{ Mandatory = $false; Position = 1 }
                [ValidateSet]::new($headers)
            )
        )
        $parameters.Add($parameter.Name, $parameter) 
        Return $parameters
    }
    Begin {
        $csvContent = Import-Csv $Path
        If ($PSBoundParameters.ContainsKey('Columns')) {
            $columns = $PSBoundParameters['Columns']
            $csvContent | Select-Object -Property $columns
        }
        Else {
            $csvContent
        }
    }
}
درون کنسول PowerShell هم یک IntelliSense برای مقادیر مجاز نمایش داده خواهد شد:

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


کوتاه سازی کدهای نمایش یک View در ASP.NET MVC با Reflection 

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

تصویر زیر را که برگرفته از یک قالب Bootstrap است، ملاحظه کنید. اصلا علاقه ندارم که برای یک به یک آن‌ها، یک سطر جدید را تعریف کنم و به View بگویم این پراپرتی را نشان بده؛ دوباره مورد بعدی هم به همین صورت و دوباره و دوباره و ... . دوست دارم یک تابع عمومی، همه‌ی این کارها را خودکار انجام دهد.

ساختار اطلاعاتی تصویر فوق به شرح زیر است:
 <div>
                              <div>
                                  <div>
                                      <p><span>First Name </span>: Jonathan</p>
                                  </div>
   </div>
                          </div>
که به دو فایل پارشال تقسیم شده است Bio_ و BioRow_ که محتویات هر پارشال هم به شرح زیر است:

BioRow_
@model System.Web.UI.WebControls.ListItem


<div>
    <p><span>@Model.Text </span>: @Model.Value</p>
</div>
در پارشال بالا ورودی از نوع listItem است که یک متن دارد و یک مقدار.(شاید به نظر شبیه حالت جفت کلید و مقدار باشد ولی در این کلاس خبری از کلید نیست).
پارشال پایینی هم دربرگیرنده‌ی پارشال بالاست که قرار است چندین و چند بار پارشال بالا در خودش نمایش دهد.
Bio_
@using System.Web.UI.WebControls
@using ZekrWepApp.Filters
@model ZekrModel.Admin

    <div>
        <h1>Bio Graph</h1>
        <div>
            
            @{
                ListItemCollection collection = GetCustomProperties.Get(Model,exclude:new string[]{"Poems","Id"});
                foreach (var item in collection)
                {
                    Html.RenderPartial(MVC.Shared.Views._BioRow, item);
                }
            }

                    </div>
    </div>
پارشال بالا یک مدل از کلاس Admin را می‌پذیرد که قرار است اطلاعات شخصی مدیر را نمایش دهد. در ابتدا متدی از یک کلاس ایستا وجود دارد که کدهای Reflection درون آن قرار دارند که یک مجموعه از ListItem‌ها را بر می‌گرداند و سپس با یک حلقه، پارشال BioRow_ را صدا می‌زند.

کد درون این کلاس ایستا را بررسی می‌کنیم؛ این کلاس دو متد دارد یکی عمومی و دیگری خصوصی است:
  public class GetCustomProperties
    {
        private static PropertyInfo[] getObjectsInfos(object obj,string[] inclue,string[] exclude )
        {
            var list = obj.GetType().GetProperties();

            PropertyInfo[] outputPropertyInfos = null;

            if (inclue != null)
            {           
                return list.Where(propertyInfo => inclue.Contains(propertyInfo.Name)).ToArray();
            }
            if (exclude != null)
            {
                return list.Where(propertyInfo => !exclude.Contains(propertyInfo.Name)).ToArray();
            }
            return list;
        }
    }
کد بالا که یک کد خصوصی است، سه پارامتر را می‌پذیرد. اولی مدل یا کلاسی است که به آن پاس کرده‌ایم. دو پارامتر بعدی اختیاری است و در کد پارشال بالا Exclude را تعریف کرده ایم و تنهای یکی از دو پارامتر بالا هم باید مورد استفاده قرار بگیرند و Include ارجحیت دارد. وظیفه‌ی این دو پارمتر این است که آرایه ای از رشته‌ها را دریافت می‌کنند که نام پراپرتی‌ها در آن‌ها ذکر شده است. آرایه Include می‌گوید که فقط این پراپرتی‌ها را برگردان ولی اگر دوست دارید همه‌ی پارامترها را نمایش دهید و تنها یکی یا چندتا از آن‌ها را حذف کنید، از آرایه Exclude استفاده کنید. در صورتی که این دو آرایه خالی باشند، همه‌ی پراپرتی‌ها بازگشت داده می‌شوند و در صورتی که یکی از آن‌ها وارد شده باشد، طبق دستورات Linq بالا بررسی می‌کند که (Include) آیا اسامی مشترکی بین آن‌ها وجود دارد یا خیر؟ اگر وجود دارد آن را در لیست قرار داده و بر می‌گرداند و در حالت Exclude این مقایسه به صورت برعکس انجام می‌گیرد و باید لیستی برگردانده شود که اسامی، نکته مشترکی نداشته باشند.

متد عمومی که در این کلاس قرار دارد به شرح زیر است:
 public static ListItemCollection Get(object obj,string[] inclue=null,string[] exclude=null)
        {
            var propertyInfos = getObjectsInfos(obj, inclue, exclude);
            if (propertyInfos == null) throw new ArgumentNullException("propertyInfos is null");

            var collection = new ListItemCollection();

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {


                string name = propertyInfo.Name;

                foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
                {
                    DisplayAttribute displayAttribute = attribute as DisplayAttribute;

                    if (displayAttribute != null)
                    {
                        name = displayAttribute.Name;
                        break;
                    }                  
                }

                string value = "";
                object objvalue = propertyInfo.GetValue(obj);
                if (objvalue != null) value = objvalue.ToString();

                collection.Add(new ListItem(name,value));
            }
            return collection;
        }
این متد سه پارامتر را از کاربر دریافت و به سمت متد خصوصی ارسال می‌کند. موقعی‌که پراپرتی‌ها بازگشت داده می‌شوند، دو قسمت آن مهم است؛ یکی عنوان پراپرتی و دیگری مقدار پراپرتی. از آن جا که نام پراپرتی‌ها طبق سلیقه‌ی برنامه نویس و با حروف انگلیسی نوشته می‌شوند، در صورتی که برنامه نویس از متادیتای Display در مدل بهره برده باشد، به جای نام پراپرتی مقداری را که به متادیتای Display داده‌ایم، بر می‌گردانیم.

کد بالا پراپرتی‌ها را دریافت و یک به یک متادیتاهای آن را بررسی کرده و در صورتی که از متادیتای Display استفاده کرده باشند، مقدار آن را جایگزین نام پراپرتی خواهد کرد. در مورد مقدار هم از آنجا که اگر پراپرتی با Null پر شده باشد، تبدیل به رشته‌ای با پیام خطای روبرو خواهد شد. در نتیجه بهتر است یک شرط احتیاط هم روی آن پیاده شود. در آخر هم از متن و مقدار، یک آیتم ساخته و درون Collection اضافه می‌کنیم و بعد از اینکه همه پراپرتی‌ها بررسی شدند، Collection را بر می‌گردانیم.
 [Display(Name = "نام کاربری")]
public string UserName { get; set; }

کد کامل کلاس:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;
using System.Web.Mvc.Html;
using System.Web.UI.WebControls;
using Links;

namespace ZekrWepApp.Filters
{
    
    public class GetCustomProperties
    {
        public static ListItemCollection Get(object obj,string[] inclue=null,string[] exclude=null)
        {
            var propertyInfos = getObjectsInfos(obj, inclue, exclude);
            if (propertyInfos == null) throw new ArgumentNullException("propertyInfos is null");

            var collection = new ListItemCollection();

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                string name = propertyInfo.Name;
                foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
                {
                    DisplayAttribute displayAttribute = attribute as DisplayAttribute;

                    if (displayAttribute != null)
                    {
                        name = displayAttribute.Name;
                        break;
                    }                  
                }

                string value = "";
                object objvalue = propertyInfo.GetValue(obj);
                if (objvalue != null) value = objvalue.ToString();

                collection.Add(new ListItem(name,value));
            }
            return collection;
        }
        private static PropertyInfo[] getObjectsInfos(object obj,string[] include,string[] exclude )
        {
            var list = obj.GetType().GetProperties();

            PropertyInfo[] outputPropertyInfos = null;

            if (include != null)
            {           
                return list.Where(propertyInfo => include.Contains(propertyInfo.Name)).ToArray();
            }
            if (exclude != null)
            {
                return list.Where(propertyInfo => !exclude.Contains(propertyInfo.Name)).ToArray();
            }
            return list;
        }  
    }   
}

لیستی از پارامترها با Reflection


مورد بعدی که ساده‌تر بوده و از کد بالا مختصرتر هم هست، این است که قرار بود برای یک درگاه، یک سری اطلاعات را با متد Post ارسال کنم که نحوه‌ی ارسال اطلاعات به شکل زیر بود:
amount=1000&orderId=452&Pid=xxx&....
کد زیر را من جهت ساخت قالب‌های این چنینی استفاده می‌کنم:

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

namespace Utils
{
    public class QueryStringParametersList
    {
        private string Symbol = "&";
        private List<KeyValuePair<string, string>> list { get; set; }

        public QueryStringParametersList()
        {
            list = new List<KeyValuePair<string, string>>();
        }
        public QueryStringParametersList(string symbol)
        {
            Symbol = symbol;
           list = new List<KeyValuePair<string, string>>(); 
        }

        public int Size
        {
            get { return list.Count; }
        }
        public void Add(string key, string value)
        {
            list.Add(new KeyValuePair<string, string>(key, value));
        }

        public string GetQueryStringPostfix()
        {
            return string.Join(Symbol, list.Select(p => Uri.EscapeDataString(p.Key) + "=" + Uri.EscapeDataString(p.Value)));
        }
    }
}



یک متغیر به نام symbol دارد و در صورتی در شرایط متفاوت، قصد چسپاندن چیزی را به یکدیگر با علامتی خاص داشته باشید، این تابع می‌تواند کاربرد داشته باشد. این متد از یک لیست کلید و مقدار استفاده کرده و پارامترهایی را که به آن پاس می‌شود، نگهداری و سپس توسط متد GetQueryStringPostfix آن‌ها را با یکدیگر الحاق کرده و در قالب یک رشته بر می‌گرداند.
کاربرد Reflection در اینجا این است که من باید دوبار به شکل زیر، دو نوع اطلاعات متفاوت را پست کنم. یکی موقع ارسال به درگاه و دیگری موقع بازگشت از درگاه.
QueryStringParametersList queryparamsList = new QueryStringParametersList();

            ueryparamsList.Add("consumer_key", requestPayment.Consumer_Key);
            queryparamsList.Add("amount", requestPayment.Amount.ToString());
            queryparamsList.Add("callback", requestPayment.Callback);
            queryparamsList.Add("description", requestPayment.Description);
            queryparamsList.Add("email", requestPayment.Email);
            queryparamsList.Add("mobile", requestPayment.Mobile);
            queryparamsList.Add("name", requestPayment.Name);
            queryparamsList.Add("irderid", requestPayment.OrderId.ToString());

ولی با استفاده از کد Reflection که در بالاتر عنوان شد، باید نام و مقدار پراپرتی را گرفته و در یک حلقه آن‌ها را اضافه کنیم، بدین شکل:
   private QueryStringParametersList ReadParams(object obj)
        {
            PropertyInfo[] propertyInfos = obj.GetType().GetProperties();

            QueryStringParametersList queryparamsList = new QueryStringParametersList();
            for (int i = 0; i < propertyInfos.Count(); i++)
            {
                queryparamsList.Add(propertyInfos[i].Name.ToLower(),propertyInfos[i].GetValue(obj).ToString() );              
            }
            return queryparamsList;
        }
در کد بالا هر بار پراپرتی‌های کلاس را خوانده و نام و مقدار آن‌ها را گرفته و به کلاس QueryString اضافه می‌کنیم. پارامتر ورودی این متد به این خاطر object در نظر گرفته شده است که تا هر کلاسی را بتوانیم به آن پاس کنیم که خودم در همین کلاس درگاه، دو کلاس را به آن پاس کردم.
مطالب
ساختارهای داده‌ی توکار ES 6
در ES 5 تنها آرایه (Array) و آبجکت (Object) را به عنوان ساختار داده‌ایی، به صورت توکار در اختیار داریم.
Array یک کالکشن مبتنی بر ایندکس است. همچنین می‌توان هر نوع مقداری را در آن ذخیره کرد:
var collection = ['a', 1, /3/, {}];
یعنی هر کدام از اعضای آرایه می‌توانند جنس متفاوتی داشته باشند. همانطور که در کد فوق مشاهده می‌کنید اعضای آرایه به ترتیب از کاراکتر، عدد، عبارت با قاعده و در نهایت یک شیء خالی تشکیل شده است. همانطور که عنوان شد آرایه‌ها در جاوا اسکریپت همانند دیگر زبان‌های برنامه‌نویسی مبتنی بر ایندکس هستند، یعنی می‌توان براساس ایندکس به هر کدام از اعضای آرایه دسترسی داشت:
collection[0];
می‌توان از پراپرتی length نیز برای دریافت سایز آرایه استفاده کرد:
collection.length
همانطور که در مثال ابتدای بحث مشاهده کردید، آرایه‌ها در جاوا اسکریپت توسط سینتکس [] قابل تعریف هستند. تعدادی تابع توکار برای کار با آرایه‌ها موجود است که در اینجا می‌توانید لیست کامل آنها را مشاهد نمائید. همچنین می‌توانید از کتابخانه‌های دیگری مانند Underscore.js که در واقع هدف آن‌ها افزودن یکسری قابلیت‌ها به جاوا اسکریپت است، استفاده کنید:
var numbers = [1, 2, 3];

_.each(numbers, function (num) {
    write(num);
});
در ES 6 تعدادی تابع جدید به Array اضافه شده که کار با آرایه‌ها را ساده‌تر کرده است. در ادامه تعدادی از این توابع را بررسی خواهیم کرد.
تابع find
این تابع از ورودی، یک callback را گرفته و نتایج یافته شده را در خروجی برمی‌گرداند:
var ary = [1, 5, 10];
var match = ary.find(item => item > 8);
تابع findIndex
این تابع مشابه تابع find عمل می‌کند با این تفاوت که در خروجی ایندکس عنصر یافته شده را برمی‌گرداند:
var match = ary.findIndex(item => item > 8);
تابع fill
از این تابع می‌توان جهت مقداردهی اعضای آرایه با پارامتر موردنظر استفاده کرد:
ary.find('a');
// خروجی ["a", "a", "a"]
لازم به ذکر است، به این تابع می‌توانیم دو پارامتر دیگر را نیز پاس دهیم:
var ary = [1, 5, 10, 5, 6];
ary.fill('a', 2, 3)
در کد فوق پارامتر دوم یعنی نقطه شروع و پارامتر سوم یعنی نقطه پایان:
// خروجی
[ 1, 5, "a", 5, 6 ]
تابع copyWithin
با کمک این تابع می‌توانیم قسمتی از یک آرایه را کپی کرده و در محل دیگری از آرایه ذخیره کنیم:
[1, 2, 3, 4, 5].copyWithin(0, 3);
کد فوق از نقطه‌ی سوم شروع به کپی کردن آیتم‌ها کرده و آنها را در موقعیت صفرم آرایه به بعد قرار می‌دهد. در نتیجه خروجی آن به این صورت است:
// [4, 5, 3, 4, 5]
لازم به ذکر است که یک پارامتر سوم را نیز می‌توانیم جهت تعیین نقطه‌ی پایان به تابع فوق اضافه کنیم:
[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// خروجی 
// [4, 2, 3, 4, 5]
در ES 6 علاوه بر سینتکس literal می‌توان از سازنده‌ی کلاس Array نیز جهت تعریف آرایه‌ها، استفاده کرد:
var ary = new Array(1, 2);
کد فوق یک آرایه را با دو مقدار 1 و 2 ایجاد می‌کند. اگر بخواهیم یک آیتم جدید را به آرایه‌ی فوق اضافه کنیم، باید آن را نیز به پارامترهای فوق اضافه کرد:
var ary = new Array(1, 2, 3);
ممکن است فکر کنید توسط کد زیر آرایه‌ایی تنها با یک آیتم برای ما ایجاد خواهد شد:
var ary = new Array(3);
در واقع کد فوق یک آرایه با اندازه‌ی سه و محتوای undefined را برای شما ایجاد خواهد کرد. در نتیجه برای ایجاد آرایه‌ایی با یک آیتم و مقدار 3 باید از متد Of کلاس Array استفاده کنیم:
var Ofary = Array.of(3);

Set
Set یک ساختار داده‌ایی جدید در ES 6 است. این ساختار داده‌ایی امکان تعریف کالکشنی از مقادیر را به صورت unique، در اختیارمان قرار می‌دهد. برخلاف آرایه‌ها مقادیر درون Set نمی‌تواند یکسان باشند. در کد زیر نحوه‌ی ایجاد یک Set نشان داده شده است:
var set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set.size); // logs 3
 Set به صورت یک مجموعه‌ی  Iterable است یعنی می‌توان اعضای این مجموعه را آیتم به آیتم پیمایش کرد. همانطور که در کد فوق مشاهده می‌کنید توسط add می‌توانیم آیتم جدیدی را به مجموعه اضافه کنیم. همچنین اگر مایل بودید می‌توانید مجموعه را توسط یک آرایه به صورت زیر نیز مقداردهی کنید:
var set = new Set([1, 2, 3]);
console.log(set.size); // logs 3
از توابع has, delete, clear نیز به ترتیب می‌توان جهت خالی کردن مجموعه، حذف یک آیتم از مجموعه و بررسی یک آیتم در مجموعه استفاده کرد:
var set = new Set();
set.has(1); // false
set.add(1);
set.has(1); // true
set.clear();
set.has(1); // false
set.add(1);
set.add(2);
set.size;   // 2
set.delete(2);
set.size;   // 1
از تابع feorach نیز می‌توانیم برای حرکت بین آیتم‌های مجموعه استفاده کنیم:
var set = new Set();
set.add('Vahid');
set.add('Sirwan');

var i = 0;
set.forEach(item => i++);
console.log(i);
همچنین از سینتکس for...of نیز می‌توان برای پیمایش مجموعه استفاده کرد:
var set = new Set();
set.add('Vahid');
set.add('Sirwan');

var i = 0;
for(let item of set) {
    i++;
}
console.log(i);
Set دارای یک تابع دیگر با نام entries است. با کمک این تابع یک iterator از مجموعه برگردانده خواهد شد که با کمک تابع next می‌توان به عناصر بعدی مجموعه دسترسی پیدا کرد:
var set = new Set();
set.add("Sirwan");
set.add(1);
set.add("Afifi");

var setIter = set.entries();

console.log(setIter.next().value); // ["Sirwan", "Sirwan"]
console.log(setIter.next().value); // [1, 1]
console.log(setIter.next().value); // ["Afifi", "Afifi"]

Map
برخلاف Set که یک مجموعه از مقادیر (values) است، Map یک مجموعه از کلید/مقدار (key/value) می‌باشد. در اینجا نیز کلیدها باید unique باشند. همچنین می‌توان از هر نوعی برای کلید استفاده کرد. برای افزودن یک مقدار به این مجموعه باید از تابع set استفاه کنیم:
var map = new Map();
map.set('name', 'Sirwan');

map.get('name'); // Sirwan
همانطور که مشاهده می‌کنید توسط تابع get نیز می‌توانیم با استفاده از کلید، به مقدار آن دسترسی داشته باشیم. همچنین می‌توانیم آرایه‌ایی از آرایه‌ها را به عنوان کلید در یک Map ذخیره کنیم:
var map = new Map([['name', 'Sirwan'], ['age', 27]]);
map.has('age'); // true
map.get('age'); // 27
map.get('name'); // Sirwan

نکته‌ایی که در استفاده از Map باید به آن دقت کنید این است که در اینجا هیچ تبدیل نوعی را بر روی کلیدها نداریم:
var map = new Map();

map.set(1, true);
map.has("1"); // false

map.set("1", true);
map.has("1"); // true
همانند Set برای Map نیز می‌توانیم از توابع delete و clear استفاده کنیم. برای استفاده از foreach باید برای callback دو پارامتر را ارائه دهیم. یکی برای value و دیگری برای key:
var map = new Map([['name', 'Sirwan'], ['age', 27]]);
var i = 0;
map.forEach(function (value, key) {
    i++;
});
console.log(i); // log 2
برای سینتکس for...of نیز می‌توانیم به اینصورت عمل کنیم:
for (var [key, value] of map) {
    i++;
}
شاید بپرسید که همین کار را می‌توان با استفاده از آرایه‌ها نیز انجام داد و چه نیازی به یک ساختار داده‌ایی جدید است؟
اگر بخواهید Map را با استفاده از آرایه‌ها شبیه‌سازی کنید باید از Associative Arrays استفاده کنید؛ به زبان ساده در این‌حالت به جای استفاده از عدد به جای ایندکس می‌توان رشته‌ها نیز استفاده کرد. به عنوان مثال کد زیر را در نظر بگیرید:
var newArray = new Array();
newArray["name"] = "Sirwan";
newArray["lastName"] = "Afifi";
در اینجا ایندکس‌ها به ترتیب name و lastName هستند و به عنوان کلید مورد استفاده قرار می‌گیرند. کلیدها نیز به مقادیر "Sirwan" و "Afifi" مپ شده‌اند. حالت فوق شبیه به یک دیکشنری عمل می‌کند. اما همانطور که عنوان شد در اینجا کلید به صورت رشته‌ایی است و نمی‌توان از اشیاء به عنوان کلید استفاده کرد؛ زیرا در نهایت تبدیل به رشته خواهند شد:
let user1 = { name: "Vahid" };
let user2 = { name: "Sirwan" };

let result = {};
result[user1] = 10;
result[user2] = 20;

console.log(result[user1]); // logs 20
console.log(result[user2]); // logs 20
در کد فوق هر کدام از شیء‌ها را به عنوان کلید در نظر گرفته‌ایم و برای هر کدام مقادیر 10 و 20 را ست کرده‌ایم. اما خروجی هر کدام 20 است؛ در حالیکه باید به ترتیب عدد 10 و سپس عدد 20 در خروجی نمایش داده شود. دلیل آن نیز کاملاً مشخص است زیرا اگر در جاوا اسکریپت برای یک شیء تابع toString را فراخوانی کنیم، مقدار "[object object]" در خروجی نمایش داده خواهد شد. در نتیجه در کد فوق در واقع هر بار ایندکس "[object object]" را به‌روز رسانی کرده‌ایم:
result["[object object]"] = 10;
result["[object object]"] = 20;

console.log(result["[object object]"]); // logs 20
console.log(result["[object object]"]); // logs 20

WeakMap and WeakSet
فرض کنید درون DOM سه عنصر div دارید و می‌خواهید این سه div را درون یک Set ذخیره کنید:

در این‌حالت آیتم‌های درون Set ارجاع مستقیمی را به عناصر موجود در DOM دارند. اکنون حالتی را در نظر بگیرید که بخواهیم یکی از عناصر موجود درون DOM را حذف کنیم. در اینحالت آیتم درون Set که به این عنصر اشاره دارد هنوز حذف نشده است و همچنان ارجاعی را به آن عنصر دارد. بنابراین تا زمانیکه آیتم از Set حذف نشود Garbage Collector نمی‌تواند حافظه‌ی اختصاص داده شده را مجدداً بازیابی کند. در نتیجه استفاده از Set و یا Map در چنین سناریوهایی منجر به نشتی حافظه خواهد شد. برای حل این مشکل می‌توانیم از WeakMap و یا WeakSet استفاده کنیم. در این‌حالت WeakMap و WeakSet ارجاع مستقیمی به اشیایی که به آنها اضافه می‌شوند، ندارند. در نتیجه GC به راحتی می‌تواند حافظه‌ی اختصاص داده شده را بعد از حذف اشیاء بازیابی کند.
صرف‌نظر از رفع مشکل حافظه، WeakMap و WeakSet شبیه به Map و Set عمل می‌کنند، اما یکسری محدودیت‌هایی در استفاده از آنها وجود دارد:
  • WeakMap و WeakSet فاقد پراپرتی‌های size, entries, values و متد foreach هستند.
  • WeakMap همچنین فاقد keys است.