public abstract class DataErrorInfo :ObservableObject, IDataErrorInfo { [Browsable(false)] public string Error { get { var errors = ValidationHelper.GetErrors(this); return string.Join(Environment.NewLine, errors); } } public string this[string columnName] { get { var errors = ValidationHelper.ValidateProperty(this, columnName); return string.Join(Environment.NewLine, errors); } } }
در Blazor میتوان مسیریابیهای پارامتری را به صورت زیر نیز تعریف کرد:
@page "/post/edit/{EditId:int}"
که در اینجا EditId، یک پارامتر مسیریابی از نوع int تعریف شده و به صورت زیر در کدهای صفحهی مرتبط، قابل دسترسی است:
[Parameter] public int? EditId { set; get; }
int تعریف شدهی در این مسیریابی، یک routing constraint و یا یک قید مسیریابی محسوب میشود و استفادهی از آن، چنین مزایایی را به همراه دارد:
- در این حالت فقط EditId های عددی پردازش میشوند و اگر رشتهای دریافت شود، کاربر با خروجی از نوع 404 و یا «یافت نشد»، مواجه خواهد شد.
- امکان اعتبارسنجی مقادیر دریافتی، پیش از ارسال آنها به صفحه و پردازش صفحه.
قیود پیشفرض تعریف شدهی در Blazor
اگر به مستندات مسیریابی Blazor مراجعه کنیم، بهنظر فقط این موارد را میتوان بهعنوان قیود پارامترهای مسیریابی تعریف کرد:
bool, datetime, decimal, double, float, guid, int, long, nonfile
و ... توضیحاتی در مورد اینکه آیا امکان بسط آنها وجود دارند یا خیر، فعلا در مستندات رسمی آن، ذکر نشدهاند.
در Blazor 8x میتوان از قیود مسیریابی سفارشی ASP.NET Core نیز استفاده کرد!
ASP.NET Core سمت سرور، به همراه امکان سفارشی سازی قیودمسیریابی خود نیز هست که آنرا میتوان به کمک اینترفیسIRouteConstraint پیاده سازی کرد:
namespace Microsoft.AspNetCore.Routing { public interface IRouteConstraint { bool Match( HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection); } }
جالب اینجا است که میتوان این نمونههای سفارشی را حداقل در نگارش جدید Blazor 8x SSR نیز استفاده کرد؛ هرچند در مستندات رسمی Blazor هنوز به آن اشارهای نشدهاست.
در امضای متد Match فوق، دو پارامتر routeKey و values آن بیش از مابقی مهم هستند:
- routeKey مشخص میکند که الان کدام پارامتر مسیریابی (مانند EditId در این مطلب) در حال پردازش است.
- values، یک دیکشنری است که کلید هر عضو آن، پارامتر مسیریابی و مقدار آن، مقدار دریافتی از URL جاری است.
- اگر این متد مقدار true را برگرداند، یعنی مسیریابی وارد شدهی به آن، با موفقیت پردازش و اعتبارسنجی شده و میتوان صفحهی مرتبط را نمایش داد؛ در غیراینصورت، کاربر پیام یافت نشدن آن صفحه و مسیر درخواستی را مشاهده میکند.
پیاده سازی یک قید سفارشی رمزگشایی پارامترهای مسیریابی
فرض کنید قصد ندارید که پارامترهای مسیریابی ویرایش رکوردهای خاصی را دقیقا بر اساس Id متناظر عددی آنها در بانک اطلاعاتی، نمایش دهید؛ برای مثال نمیخواهید دقیقا آدرس post/edit/1 را به کاربر نمایش دهید؛ چون نمایش این اعداد عموما ساده و ترتیبی، حدس زدن آنها را ساده کرده و ممکن است در آینده مشکلات امنیتی را به همراه داشته باشد.
میخواهیم از آدرسهای متداول و سادهی عددی زیر:
@page "/post/edit/{EditId:int}"
به آدرس رمزنگاری شدهی زیر برسیم:
@page "/post/edit/{EditId:encrypt}"
اگر به این آدرس جدید دقت کنید، در اینجا از نام قید جدیدی به نام encrypt استفاده شدهاست که جزو قیود پیشفرض سیستم مسیریابی Blazor نیست. روش تعریف آن به صورت زیر است:
using System.Globalization; using DNTCommon.Web.Core; namespace Blazor8xSsrComponents.Utils; public class EncryptedRouteConstraint(IProtectionProviderService protectionProvider) : IRouteConstraint { public const string Name = "encrypt"; public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { ArgumentNullException.ThrowIfNull(routeKey); ArgumentNullException.ThrowIfNull(values); if (!values.TryGetValue(routeKey, out var routeValue)) { return false; } var valueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture); values[routeKey] = string.IsNullOrEmpty(valueString) ? null : protectionProvider.Decrypt(valueString); return true; } }
توضیحات:
- در قیود سفارشی میتوان سرویسها را به سازندهی کلاس تزریق کرد و برای مثال از سرویس IProtectionProviderService که در کتابخانهی DNTCommon.Web.Core تعریف شده، برای رمزگشایی اطلاعات رسیده، استفاده کردهایم.
- یک نام را هم برای آن درنظر گرفتهایم که از این نام در فایل Program.cs به صورت زیر استفاده میشود:
builder.Services.Configure<RouteOptions>(opt => { opt.ConstraintMap.Add(EncryptedRouteConstraint.Name, typeof(EncryptedRouteConstraint)); });
یعنی زمانیکه سیستم مسیریابی به قید جدیدی به نام encrypt میرسد:
@page "/post/edit/{EditId:encrypt}"
آنرا در لیست ConstraintMap ای که به نحو فوق به سیستم معرفی شده، جستجو میکند. اگر این نام یافت شد، سپس کلاس EncryptedRouteConstraint متناظر را نمونه سازی کرده و در جهت پردازش مسیر رسیده، مورد استفاده قرار میدهد.
- در کلاس EncryptedRouteConstraint و متد Match آن، مقدار رشتهای EditId دریافت شدهی از طریق آدرس جاری درخواستی، رمزگشایی شده و بجای مقدار فعلی رمزنگاری شدهی آن درج میشود. همین اندازه برای مقدار دهی خودکار پارامتر EditId ذیل در صفحات مرتبط، کفایت میکند:
[Parameter] public string? EditId { set; get; }
یعنی دیگر نیازی نیست تا در صفحات مرتبط، کار رمزگشایی EditId، به صورت دستی انجام شود.
در NHibernate چندین و چند روش، جهت تهیه کوئریها وجود دارد که QueryOver یکی از آنها است (+). QueryOver نسبت به LINQ to NH سازگاری بهتری با ساز و کار درونی NHibernate دارد؛ برای مثال امکان یکپارچگی آن با سطح دوم کش. هر چند ظاهر QueryOver با LINQ یکی است، اما در عمل متفاوتند و راه و روش خاص خودش را طلب میکند. برای مثال در LINQ to NH میتواند نوشت x.Property.Contains اما در QueryOver متدی به نام contains قابل استفاده نیست (هر چند در Intellisense ظاهر میشود اما عملا تعریف نشده است و نباید آنرا با LINQ اشتباه گرفت) و سعی در استفاده از آنها به استثناهای زیر ختم میشوند:
Unrecognised method call: System.String:Boolean StartsWith(System.String)
Unrecognised method call: System.String:Boolean Contains(System.String)
using NHibernate.Validator.Constraints;
namespace NH3Test.MappingDefinitions.Domain
{
public class Account
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
[NotNull]
public virtual int Balance { set; get; }
}
}
1) یافتن رکوردهایی که در یک مجموعهی مشخص قرار دارند. برای مثال balance آنها مساوی 10 و 12 است:
var list = new[] { 12,10};
var resultList = session.QueryOver<Account>()
.WhereRestrictionOn(p => p.Balance)
.IsIn(list)
.List();
SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_
FROM
Accounts this_
WHERE
this_.Balance in (
@p0 /* = 10 */, @p1 /* = 12 */
)
2) پیاده سازی همان متد Contains ذکر شده، در QueryOver:
var accountsContianX = session.QueryOver<Account>()
.WhereRestrictionOn(x => x.Name)
.IsLike("X", NHibernate.Criterion.MatchMode.Anywhere)
.List();
SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_
FROM
Accounts this_
WHERE
this_.Name like @p0 /* = %X% */
در اینجا بر اساس مقادیر مختلف MatchMode میتوان متدهای StartsWith (MatchMode.Start) ، EndsWith (MatchMode.End) ، Equals (MatchMode.Exact) را نیز تهیه نمود.
انجام مثال دوم راه سادهتری نیز دارد. قسمت WhereRestrictionOn و IsLike به صورت یک سری extension متد ویژه در فضای نام NHibernate.Criterion تعریف شدهاند. ابتدا این فضای نام را به کلاس جاری افزوده و سپس میتوان نوشت :
using NHibernate.Criterion;
...
var accountsContianX = session.QueryOver<Account>()
.Where(x => x.Name.IsLike("%X%"))
.List();
این فضای نام شامل چهار extension method به نامهای IsLike ، IsInsensitiveLike ، IsIn و IsBetween است.
چگونه extension method سفارشی خود را تهیه کنیم؟
بهترین کار این است که به سورس NHibernate ، فایلهای RestrictionsExtensions.cs و ExpressionProcessor.cs که تعاریف متد IsLike در آنها وجود دارد مراجعه کرد. در اینجا میتوان با نحوهی تعریف و سپس ثبت آن در رجیستری extension methods مرتبط با QueryOver توسط متد عمومی RegisterCustomMethodCall آشنا شد. در ادامه سه کار را میتوان انجام داد:
-متد مورد نظر را در کدهای خود (نه کدهای اصلی NH) اضافه کرده و سپس با فراخوانی RegisterCustomMethodCall آنرا قابل استفاده نمائید.
-متد خود را به سورس اصلی NH اضافه کرده و کامپایل کنید.
-متد خود را به سورس اصلی NH اضافه کرده و کامپایل کنید (بهتر است همان روش نامگذاری بکار گرفته شده در فایلهای ذکر شده رعایت شود). یک تست هم برای آن بنویسید (تست نویسی هم یک سری اصولی دارد (+)). سپس یک patch از آن روی آن ساخته (+) و برای تیم NH ارسال نمائید (تا جایی که دقت کردم از کلیه ارسالهایی که آزمون واحد نداشته باشند، صرفنظر میشود).
مثال:
میخواهیم extension متد جدیدی به نام Year را به QueryOver اضافه کنیم. این متد را هم بر اساس توابع توکار بانکهای اطلاعاتی، تهیه خواهیم نمود. لیست کامل این نوع متدهای بومی SQL را در فایل Dialect.cs سورسهای NH میتوان یافت (البته به صورت پیش فرض از متد extract برای جداسازی قسمتهای مختلف تاریخ استفاده میکند. این متد در فایلهای Dialect مربوط به بانکهای اطلاعاتی مختلف، متفاوت است و برحسب بانک اطلاعاتی جاری به صورت خودکار تغییر خواهد کرد).
using System;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Impl;
namespace NH3Test.ConsoleApplication
{
public static class MyQueryOverExts
{
public static bool YearIs(this DateTime projection, int year)
{
throw new Exception("Not to be used directly - use inside QueryOver expression");
}
public static ICriterion ProcessAnsiYear(MethodCallExpression methodCallExpression)
{
string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
object value = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
return Restrictions.Eq(
Projections.SqlFunction("year", NHibernateUtil.DateTime, Projections.Property(property)),
value);
}
}
public class QueryOverExtsRegistry
{
public static void RegistrMyQueryOverExts()
{
ExpressionProcessor.RegisterCustomMethodCall(
() => MyQueryOverExts.YearIs(DateTime.Now, 0),
MyQueryOverExts.ProcessAnsiYear);
}
}
}
اکنون برای استفاده خواهیم داشت:
QueryOverExtsRegistry.RegistrMyQueryOverExts(); //یکبار در ابتدای اجرای برنامه باید ثبت شود
...
var data = session.QueryOver<Account>()
.Where(x => x.AddDate.YearIs(2010))
.List();
برای مثال اگر بانک اطلاعاتی انتخابی از نوع SQLite باشد، خروجی SQL مرتبط به شکل زیر خواهد بود:
SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_,
this_.AddDate as AddDate0_0_
FROM
Accounts this_
WHERE
strftime("%Y", this_.AddDate) = @p0 /* =2010 */
هر چند ما تابع year را در متد ProcessAnsiYear ثبت کردهایم اما بر اساس فایل SQLiteDialect.cs ، تعاریف مرتبط و مخصوص این بانک اطلاعاتی (مانند متد strftime فوق) به صورت خودکار دریافت میگردد و کد ما مستقل از نوع بانک اطلاعاتی خواهد بود.
نکته جالب!
LINQ to NH هم قابل بسط است؛ کاری که در ORM های دیگر به این سادگی نیست. چند مثال در این زمینه:
چگونه تابع سفارشی SQL Server خود را به صورت یک extension method تعریف و استفاده کنیم: (+) ، یک نمونه دیگر: (+) و نمونهای دیگر: (+).
در ادامه مطلب قبلی، یکی از مشکلاتی که طراحی Builder از آن رنج میبرد، نقض کردن قانون command query separation است که در ادامه دربارهی این اصل بیشتر بحث خواهیم کرد.
اصل Command query separation یا به اختصار CQS، در کتاب Object-Oriented Software Construction توسط Bertrand Meyer معرفی شدهاست. بر اساس آن، عملیاتهای سیستم باید یا Command باشند و یا Query و نه هر دوی آنها. وقتی یک کلاینت به امضای یک متد توجه میکند، اینکه این متد چه کاری را انجام میدهد Commands نام داشته و به شیء فرمان میدهد تا کاری را انجام بدهد. این عملیات وضعیت خود شیء و یا اشیاء دیگر را تغییر میدهد. در اینجا Queries به شیء فرمان میدهند تا نتیجهی سؤال ( ویا درخواست) را برگرداند.
در آن سوی دیگر، متدهایی را که وضعیت شیء را تغییر میدهند، به عنوان Command در نظر میگیریم (بدون آنکه مقداری را برگردانند). اگر این نوع متدها، مقداری را برگردانند، باعث سردرگمی کلاینت میشوند؛ زیرا کلاینت نمیداند این متد باعث تغییر شیء شدهاست و یا Query؟
public class FileStore { public string WorkingDirectory { get; set; } public string Save(int id, string message) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); File.WriteAllText(path, message); return path; } public event EventHandler<MessageEventArgs> MessageRead; public void Read(int id) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); var msg = File.ReadAllText(path); this.MessageRead(this, new MessageEventArgs { Message = msg }); } }
public string Read(int id) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); var msg = File.ReadAllText(path); this.MessageRead(this, new MessageEventArgs { Message = msg }); return msg; }
public void Save(int id, string message) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); File.WriteAllText(path, message); }
public class FileStore { public string WorkingDirectory { get; set; } public void Save(int id, string message) { var path = GetFileName(id); //ok to query from Command File.WriteAllText(path, message); } public string Read(int id) { var path = GetFileName(id); var msg = File.ReadAllText(path); return msg; } public string GetFileName(int id) { return Path.Combine(this.WorkingDirectory , id + ".txt"); } }
چکیده:
هدف اصلی از طراحی نرم افزار، غالب شدن بر پیچیدگیها میباشد. اصل CQS متدها را به دو دستهی Command و Query تقسیم میکند که Query ، اطلاعاتی را از وضعیت سیستم بر میگرداند، ولی command وضعیت سیستم را تغییر میدهد و مهمترین دستاورد CQS ما را به سمت کدی تمیزتر و با قابلیت درک بهتر میرساند.
class firstCLs { doFirst(str) { console.log(str); } } class secondCls extends firstCLs { doSecond(str) { super.doFirst(str); } } let str = 'string'; new secondCls().doSecond(`this is some ${str}`);
npm install -g gulp
npm install gulp --save-dev
npm install --save-dev gulp-babel babel-preset-es2015
const gulp = require('gulp'); const babel = require('gulp-babel'); gulp.task('default', () => { return gulp.src('src/code.js') .pipe(babel({ presets: ['es2015'] })) .pipe(gulp.dest('dist')); });
npm install -g lebab
'use strict'; // Let/const var name = 'Bob', time = 'yesterday'; time = 'today'; // Template string console.log('Hello ' + name + ', how are you ' + time + '?'); var bob = { // Object shorthand name: name, // Object method sayMyName: function () { console.log(this.name); } }; // Classes var SkinnedMesh = function SkinnedMesh() { }; SkinnedMesh.prototype.update = function (camera) { camera = camera || createCamera(); this.camera = camera; }; Object.defineProperty(SkinnedMesh.prototype, 'name', { set: function (geometry) { this.geometry = geometry; }, get: function () { return this.geometry; } }); // Commonjs var lebab = require('lebab'); module.exports = SkinnedMesh; // Arrow functions var render = function () { // ... requestAnimationFrame(render); };
lebab es5.js -o es6.js
const name = 'Bob'; let time = 'yesterday'; time = 'today'; // Template string console.log(`Hello ${name}, how are you ${time}?`); const bob = { // Object shorthand name, // Object method sayMyName() { console.log(this.name); } }; class SkinnedMesh { update(camera=createCamera()) { this.camera = camera; } set name(geometry) { this.geometry = geometry; } get name() { return this.geometry; } } import lebab from 'lebab'; export default SkinnedMesh; // Arrow functions const render = () => { // ... requestAnimationFrame(render); };
کار با IDE و حرکت بین کدها
در ادامه، همان پروژهای را که در قسمت قبل ایجاد کردیم، مجددا با وارد شدن به پوشهی آن و اجرای دستور . code، توسط VSCode باز خواهیم کرد. سپس فایل Program.cs آنرا باز کنید. فرض کنید در سطر ذیل آن:
.UseStartup<Startup>()
الف) اشارهگر ماوس را به آن نزدیک کنید و سپس دکمهی Ctrl را نگه دارید. به این ترتیب واژهی Startup تبدیل به یک لینک خواهد شد که با کلیک بر روی آن میتوان به کلاس Startup رسید.
ب) روش دوم، قرار دادن اشارهگر متنی بر روی واژه Startup و سپس فشردن دکمهی F12 است. این گزینه بر روی منوی کلیک راست بر روی این واژه نیز وجود دارد.
اگر دکمهی F12 را بر روی کلاسی فشار دهید که کدهای آن در IDE وجود ندارند، یک صفحهی جدید باز شده و کلاس تعریف آنرا بر اساس ساختار و متادیتای این اسمبلی، به همراه مستندات کامل آنها نمایش میدهد.
در این حالت اگر خواستید به مکان قبلی بازگردید فقط کافی است دکمههای alt + left cursor key را بفشارید.
در اینجا امکان یافتن ارجاعات به یک کلاس، مشاهدهی پیاده سازیها و همچنین یک Refactoring به نام rename symbol نیز موجود است که با استفادهی از آن، تمام ارجاعات به این کلاس در IDE نیز تغییرنام خواهند یافت.
یافتن سریع فایلها در IDE
در یک پروژهی بزرگ، برای یافتن سریع یک فایل، تنها کافی است دکمههای Ctrl+P را فشرده و در صفحهی دیالوگ باز شده، قسمتی از نام آنرا جستجو کنید:
همچنین اگر میخواهید محتوای فایلها را جهت یافتن واژهای خاص جستجو کنید، کلیدهای Ctrl+Shift+F (منوی Edit بالای صفحه و یا دومین آیکن در نوار ابزار عمودی سمت چپ صفحه) چنین امکانی را فراهم میکنند:
افزودن فایلهای جدید به پروژه
فرض کنید میخواهیم یک کنترلر جدید را به پوشهی Controllers اضافه کنیم:
برای اینکار ابتدا پوشهی Controllers را انتخاب کنید. در همین حال، نوار ابزاری ظاهر میشود که توسط آن میتوان در این پوشه، یک فایل جدید و یا یک پوشهی جدید را ایجاد کرد.
اگر علاقمند به ایجاد فایل یا پوشهای در ریشهی پروژه باشید، باید تمام فایلها و پوشههای موجود، در حالت انتخاب نشده قرار بگیرند. به همین جهت فقط کافی است در یک مکان خالی، در لیست فایلها کلیک کنید تا فایلها و پوشهها از حالت انتخاب شده خارج شوند. اکنون نوار ابزار ظاهر شدهی افزودن فایلها، به پوشهی ریشهی پروژه اشاره میکند.
در ادامه فایل جدید AboutController.cs را در پوشهی Controllers ایجاد کنید. مشاهده خواهید کرد که یک فایل کاملا خالی ایجاد شدهاست. در VSCode به ازای پسوندهای مختلف فایلها، قالبهای از پیش آماده شدهای برای آنها درنظر گرفته نمیشود و نمای ابتدایی تمام آنها خالی است.
اما در اینجا اگر کلمهی name را تایپ کنیم، دو پیشنهاد افزودن فضای نام را ارائه میدهد:
اولی صرفا نام namespace را در صفحه درج خواهد کرد. دومی به یک code snippet اشاره میکند و کار آن ایجاد قالب یک فضای نام جدید است. برای درج آن فقط کافی است دکمهی tab را بفشارید.
همین کار را در مورد class نیز میتوان تکرار کرد و در اینجا در intellisense ظاهر شده یا میتوان واژهی class را درج کرد و یا code snippet آنرا انتخاب نمود که یک کلاس جدید را ایجاد میکند:
یک نکته: در VSCode نیازی نیست تا مدام دکمههای Ctrl+S را جهت ذخیره سازی فایلهای تغییر کرده فشرد. میتوان از منوی فایل، گزینهی Auto Save را انتخاب کرد تا اینکار را به صورت خودکار انجام دهد.
ایجاد Code Snippets جدید
هرچند تا اینجا با استفاده از code snippets پیش فرض فضاهای نام و کلاسها، یک کلاس جدید را ایجاد کردیم، اما روش سادهتری نیز برای انجام اینکارها و تکمیل کنترلر وجود دارد.
برای این منظور به منوی File -> Preferences -> User snippets مراجعه کنید و سپس از لیست ظاهر شده، زبان #C را انتخاب کنید:
به این ترتیب یک قالب جدید code snippet تولید خواهد شد. در اینجا میخواهیم برای تولید Actionهای یک کنترلر نیز یک code snippet جدید را تهیه کنیم:
{ "MVC Action": { "prefix": "mvcAction", "body": [ "public IActionResult ${1:ActionName}()", "{", " $0 ", " return View();", "}" ], "description": "Creates a simple MVC action method." } }
- در این آرایه، 0$ جائی است که اشارهگر متنی پس از درج snippet قرار میگیرد و 1$ به نامی که قرار است توسط کاربر تکمیل شود، اشاره میکند که در اینجا یک نام پیش فرض مانند ActionName را هم میتوان برای آن درنظر گرفت.
- در اینجا prefix نامی است که اگر در صفحه تایپ شود، منوی انتخاب این code snippet را ظاهر میکند:
استفاده از بستههای Code Snippets آماده
خوشبختانه پشتیبانی جامعهی توسعه دهندگان از VSCode بسیار مطلوب است و علاوه بر افزونههای قابل توجهی که برای آن نوشته شدهاند، بستههای Code Snippets آمادهای نیز جهت بالابردن سرعت کار با آن وجود دارند. برای دریافت آنها، به نوار ابزار عمودی که در سمت چپ صفحه وجود دارد، مراجعه کنید و گزینهی extensions آنرا انتخاب نمائید:
در اینجا اگر aspnetcore را جستجو کنید، لیست بستههای code snippets برچسب گذاری شدهی با aspnetcore ظاهر میشود. در همینجا اگر یکی از آنها را انتخاب کنید، در سمت راست صفحه میتوانید توضیحات آن را نیز مشاهده و مطالعه نمائید (بدون نیاز به خروج از IDE).
برای مثال wilderminds-aspnetcore-snippets را نصب کنید. پس از آن تنها کافی است mvc6 را درون یک کلاس کنترلر تایپ نمائید تا امکانات آن ظاهر شوند:
برای نمونه پس از ایجاد یک فایل خالی کنترلر، انتخاب code snippet ایی به نام mvc6-controller، سبب ایجاد یک کنترلر کامل به همراه اکشن متدهایی پیش فرض میشود. بنابراین معادل قالبهای new items در نگارش کامل ویژوال استودیو در اینجا میتوان از Code Snippets استفاده کرد.
درکل برای یافتن مواردی مشابه، بهتر است واژهی کلیدی snippets را در قسمت extensions جستجو نمائید و آنها صرفا مختص به #C یا ASP.NET Core نیستند.
مدیریت ارجاعات و بستههای نیوگت در برنامههای ASP.NET Core
تا اینجا مدیریت فایلها و نحوهی تکمیل آنها را توسط code snippets، بررسی کردیم. قدم بعدی و گردش کاری مهم مورد نیاز دیگر، نحوهی افزودن ارجاعات به پروژه در VSCode است.
مدیریت ارجاعات در نگارشهای جدید ASP.NET Core، در فایل csproj برنامه انجام میشوند. در اینجا است که بستههای نیوگت جدید، ارجاعات به پروژههای دیگر و حتی شماره فریم ورک مورد استفاده تعیین میشوند.
برای نمونه بستهی نیوگت DNTPersianUtils.Core را به لیست ارجاعات آن اضافه کنید:
<PackageReference Include="DNTPersianUtils.Core" Version="2.2.0" />
در اینجا با کلیک بر روی لینک و یا دکمهی restore ، کار دریافت این بسته و نصب آن انجام خواهد شد.
اگر علاقمند بودید تا اینکار را در خط فرمان به صورت دستی انجام دهید، دکمههای Ctrl+ back-tick را فشرده، تا امکانات خط فرمان درون VSCode ظاهر شوند و سپس دستور dotnet restore را صادر کنید.
روش دوم ثبت بستههای نیوگت در فایل csproj، مراجعه به خط فرمان (فشردن دکمههای دکمههای Ctrl+ back-tick) و صدور دستور ذیل است:
> dotnet add package DNTPersianUtils.Core
دیباگ برنامههای ASP.NET Core در VSCode
در قسمت قبل با فرامین dotnet run و dotnet build و همچنین نحوهی اجرای سریع آنها آشنا شدیم. در ادامه اگر به نوار ابزار عمودی کنار صفحهی آن دقت کنید، گزینهی دیباگ نیز وجود دارد:
در اینجا دو نوع نحوهی برپایی و اجرای برنامه را مشاهده میکنید که هر دو مورد در فایل vscode\launch.json. زمانیکه دیباگ پروژه را در ابتدای باز کردن آن در VSCode فعال میکنیم، تعریف شدهاند.
برای بررسی آن فایل HomeController.cs را گشوده و در ابتدای متد About آن یک break point را قرار دهید (با حرکت دادن اشارهگر ماوس، جائیکه شماره سطرها قرار دارند، علامت درج break point ظاهر میشود که با کلیک بعدی، دائمی خواهد شد و برعکس):
پس از آن تنها کاری که باید انجام داد، فشردن دکمهی F5 است که به معنای اجرای برنامه به همراه اتصال دیباگر به آن میباشد (در قسمت قبل، Ctrl+F5 را بررسی کردیم که به معنای اجرای برنامه، بدون اتصال دیباگر به آن است).
در ادامه پس از اجرای برنامه، بر روی لینک About کلیک کنید تا اکشن متد آن اجرا شود. بلافاصله کنترل کار به VSCode بازگشته و سطری که بر روی آن break-point قرار داده بودیم، ظاهر میشود:
اطلاعات بیشتر آن در برگهی دیباگ ظاهر میشوند و در کل تجربهی کاربری آن همانند سایر IDEهایی است که تاکنون با آنها کار کردهاید.
یک نکته: در اینجا در داخل فایلهای View (فایلهای razor) نیز میتوان break point قرار داد.
برپایی یک Watcher Build
ابزار ویژهای به همراه ابزارهای Build مخصوص پروژههای NET Core. وجود دارد به نام watcher که تغییرات پوشههای برنامه را تحت نظر قرار داده و با هر تغییری، پروژه را کامپایل میکند. به این ترتیب به سرعت میتوان آخرین تغییرات برنامه را در مرورگر بررسی کرد. برای نصب آن، تنظیم ذیل را به فایل csproj برنامه اضافه کنید:
<ItemGroup> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> </ItemGroup>
اکنون برای استفادهی از آن تنها کافی است دستور ذیل را صادر کنید:
>dotnet watch run ?[90mwatch : ?[39mStarted Hosting environment: Production Content root path: D:\vs-code-examples\FirstAspNetCoreProject Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down.
یک نکته: اینبار برای دیباگ برنامه، باید گزینهی attach را انتخاب کرد:
اگر بر روی دکمهی سبز رنگ کنار آن کلیک کنید، لیست پروسههای دات نتی ظاهر شده و در این حالت میتوانید دیباگر را به پروسهی dotnet exec ایی که به dll برنامه اشاره میکند، متصل کنید (و نه پروسهی dotnet watch run که در حقیقت پروسهی dotnet exec را مدیریت میکند).
Interceptors پایهی پروکسیهای پویا هستند
برای پیاده سازی پروکسیهای پویا نیاز است با مفهوم Interceptors آشنا شویم. به کمک Interceptors فرآیند فراخوانی متدها و خواص یک کلاس، تحت کنترل و نظارت قرار خواهند گرفت. زمانیکه یک IOC Container کار وهله سازی کلاس سرویس خاصی را انجام میدهد، در همین حین میتوان مراحل شروع، پایان و خطاهای متدها یا فراخوانیهای خواص را نیز تحت نظر قرار داد و به این ترتیب مصرف کننده، امکان تزریق کدهایی را در این مکانها خواهد یافت. مزیت مهم استفاده از Interceptors، عدم نیاز به کامپایل ثانویه اسمبلیهای موجود، برای تغییری در کدهای آنها است (برای تزریق نواحی تحت کنترل قرار دادن اعمال) و تمام کارها به صورت خودکار در زمان اجرای برنامه مدیریت میگردند.
با اضافه کردن Interception به پروسه وهله سازی سرویسها توسط یک IoC Container، مراحل کار اینبار به صورت زیر تغییر میکنند:
الف) در اینجا نیز در ابتدا فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص، به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده را بر اساس تنظیمات اولیهی خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Decorator را سبب خواهد شد.
یعنی به صورت خلاصه، فراخوان سرویسی را درخواست میدهد، اما وهلهای را که دریافت میکند، یک لایهی اضافهتر تزئین کننده را نیز به همراه دارد که کاملا از دید فراخوان مخفی است و نحوهی کار کردن با آن سرویس، با و بدون این تزئین کننده، دقیقا یکی است. وجود این لایهی تزئین کننده سبب میشود تا فراخوانی هر متد این سرویس، از این لایه گذشته و سبب اجرای یک سری کد سفارشی، پیش و پس از اجرای این متد نیز گردد.
پیاده سازی پروکسیهای پویا توسط کتابخانهی Castle.Core در برنامههای NET Core.
در ادامه از کتابخانهی بسیار معروف Castle.Core برای پیاده سازی پروکسیهای پویا استفاده خواهیم کرد. از این کتابخانه در پروژهی EF Core، برای پیاده سازی Lazy loading نیز استفاده شدهاست.
برای دریافت آن یکی از دستورات زیر را اجرا نمائید:
> Install-Package Castle.Core > dotnet add package Castle.Core
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="castle.core" Version="4.3.1" /> </ItemGroup> </Project>
تبدیل ExceptionHandlingDecorator مثال قسمت قبل، به یک Interceptor مخصوص Castle.Core
در قسمت قبل، کلاس MyTaskServiceDecorator را جهت اعمال یک try/catch به همراه logging، به متد Run سرویس MyTaskService، تهیه کردیم. در اینجا قصد داریم نگارش عمومیتر این تزئین کننده را با طراحی یک Interceptor مخصوص Castle.Core انجام دهیم:
using System; using Castle.DynamicProxy; using Microsoft.Extensions.Logging; namespace CoreIocSample02.Utils { public class ExceptionHandlingInterceptor : IInterceptor { private readonly ILogger<ExceptionHandlingInterceptor> _logger; public ExceptionHandlingInterceptor(ILogger<ExceptionHandlingInterceptor> logger) { _logger = logger; } public void Intercept(IInvocation invocation) { try { invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت میگیرد } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } } } }
public void Run() { try { _decorated.Run(); } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } }
اتصال ExceptionHandlingInterceptor تهیه شده به سیستم تزریق وابستگیها
در ادامه روش معرفی ExceptionHandlingInterceptor تهیه شده را به سیستم تزریق وابستگیها، توسط متد Decorate کتابخانهی Scrutor که آنرا در قسمت قبل بررسی کردیم، ملاحظه میکنید:
namespace CoreIocSample02 { public class Startup { private static readonly ProxyGenerator _dynamicProxy = new ProxyGenerator(); public void ConfigureServices(IServiceCollection services) { services.AddTransient<ITaskService, MyTaskService>(); services.AddTransient<ExceptionHandlingInterceptor>(); services.Decorate(typeof(ITaskService), (target, serviceProvider) => _dynamicProxy.CreateInterfaceProxyWithTargetInterface( interfaceToProxy: typeof(ITaskService), target: target, interceptors: serviceProvider.GetRequiredService<ExceptionHandlingInterceptor>()) );
- سپس نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود.
- در ادامه توسط متد الحاقی Decorate، کار تزئین ITaskService را با یک dynamicProxy که ExceptionHandlingInterceptor را به صورت پویا تبدیل به یک Decorator کرده و بر روی تک تک متدهای سرویس ITaskService اجرا میکند، انجام میدهیم.
- کاری که Scrutor در اینجا انجام میدهد، یافتن سرویس ITaskService معرفی شدهی پیشین و تعویض آن با dynamicProxy میباشد. بنابراین نیاز است تعریف services.AddTransient، پیش از تعریف services.Decorate انجام شده باشد.
یک نکته: چون ExceptionHandlingInterceptor دارای پارامتر تزریق شدهای در سازندهی آن است، بهتر است خود آنرا نیز به صورت یک سرویس ثبت کنیم و سپس وهلهای از آنرا از طریق serviceProvider.GetRequiredService در قسمت interceptors متد CreateInterfaceProxyWithTargetInterface معرفی کنیم تا نیازی به مقدار دهی دستی تک تک پارامترهای سازندهی آن نباشد.
اکنون اگر برنامه را اجرا کنیم و برای مثال ITaskService را در سازندهی یک کنترلر تزریق کنیم، بجای دریافت وهلهای از کلاس MyTaskService، اینبار وهلهای از Castle.Proxies.ITaskServiceProxy را دریافت میکنیم.
به این معنا که Castle.Core به صورت پویا وهلهی سرویس MyTaskService را داخل یک Castle.Proxies پیچیدهاست و از این پس ما از طریق این واسط، با سرویس اصلی MyTaskService ارتباط برقرار خواهیم کرد. برای درک بهتر این مراحل، بر روی سازندهی کلاس کنترلر و همچنین متد Intercept، تعدادی break-point را قرار دهید.
ASP.NET MVC #12
+ و یا همچنین layout، مدل محتوای خودش را به ارث میبرد. یعنی مدلی که در View تنظیم میشود، همان مدلی است که layout به آن دسترسی خواهد داشت. به همین جهت مثلا میتونید توسط ViewBag، عنوان صفحه را که در layout تعریف شده، مقدار دهی کنید.
اگر میخواهید Strongly typed کار کنید، روش Html.RenderAction یک راه حل است و روش دوم به صورت زیر است:
یک کلاس پایه abstract تعریف کنید:
public abstract class BaseViewModel { public string Name { get; set; } }
public class HomeViewModel : BaseViewModel { public int Data1 { set; get;} // ... }
@model BaseViewModel <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Test</title> </head> <body> <header> Hello @Model.Name </header> <div> @this.RenderBody() </div> </body> </html>
Blazor 5x - قسمت هفتم - مبانی Blazor - بخش 4 - انتقال اطلاعات از کامپوننتهای فرزند به کامپوننت والد
<div class="bg-light border p-2 col-5 offset-1 mt-2" @onclick="@(() => AmenitySelectionChanged(Amenity.Name))"> <h4 class="text-secondary">Amenity - @Amenity.Id</h4> @Amenity.Name<br /> @Amenity.Description<br /> </div> @code { [Parameter] public BlazorAmenity Amenity { get; set; } [Parameter] public EventCallback<string> OnAmenitySelection { get; set; } protected async Task AmenitySelectionChanged(string name) { await OnAmenitySelection.InvokeAsync(name); } }
در مطلب پیشین به پیرامون خود نگاه کردیم و اشیاء گوناگونی را مشاهده کردیم که در حقیقت دنیای ما را تشکیل داده اند و فعالیتهای روزمره ما با استفاده از آنها صورت میگیرد. ایده ای به ذهنمان رسید. اشیاء و مفاهیم مرتبط به آن میتواند روش بهتر و موثرتری برای تقسیم کدهای برنامه باشد. مثلاً اگر کل کدهای برنامه که مسئول حل یکی از مسئلههای کوچک یاد شده است را یکجا بسته بندی کنیم و اصولی که از اشیاء واقعی پیرامون خود آموختیم را در مورد آن رعایت کنیم به برنامه بسیار با کیفیتتری از نظر خوانایی، راحتی در توسعه، اشکال زدایی سادهتر و بسیاری موارد دیگر خواهیم رسید.
توسعه دهندگان زبانهای برنامه نویسی که با ما در این مورد هم عقیده بوده اند دست به کار شده و دستورات و ساختارهای لازم برای پیاده کردن این ایده را در زبان برنامه نویسی قرار دادند و آن را زبان برنامه نویسی شیء گرا نامیدند. حتی جهت برخورداری از قابلیت استفاده مجدد از کد و موارد دیگر به جای آنکه کدها را در بسته هایی به عنوان یک شیء خاص قرار دهیم آنها را در بسته هایی به عنوان قالب یا نقشه ساخت اشیاء خاصی که در ذهن داریم قرار میدهیم. یعنی مفهوم کلاس یا رده که پیشتر اشاره شد. به این ترتیب یک بار مینویسیم و بارها استفاده میکنیم. مانند همان مثال بازیکن در بخش نخست. هر زمان که لازم باشد با استفاده از دستورات مربوطه از روی کدهای کلاس که نقشه یا قالب ساخت اشیاء هستند شیء مورد نظر را ساخته و در جهت حل مسئله مورد نظر به کار میبریم.
حال برای آنکه به طور عملی بتوانیم از ایده شیء گرایی در برنامه هایمان استفاده کنیم و مسائل بزرگ را حل کنیم لازم است ابتدا مقداری با جزییات و دستورات زبان در این مورد آشنا شویم.
تذکر: دقت کنید برای آنکه از ایده شیء گرایی در برنامهها حداکثر استفاده را ببریم مفاهیمی در مهندسی نرم افزار به آن اضافه شده است که ممکن است در دنیای واقعی نیازی به طرح آنها نباشد. پس لطفاً تلاش نکنید با دیدن هر مفهوم تازه بلافاصله سعی در تطبیق آن با محیط اطراف کنید. هر چند بسیاری از آنها به طور ضمنی در اشیاء پیرامون ما نیز وجود دارند.
زبان برنامه نویسی مورد استفاده برای بیان مفاهیم برنامه نویسی در این سری مقالات زبان سی شارپ است. اما درک برنامههای نوشته شده برای علاقه مندان به زبانهای دیگری مانند وی بی دات نت نیز دشوار نیست. چراکه اکثر دستورات مشابه است و تبدیل Syntax نیز به راحتی با اندکی جستجو میسر میباشد. لازم به یادآوری است زبان سی شارپ به بزرگی یا کوچکی حروف حساس است.
تشخیص و تعریف کلاسهای برنامه
کار را با یک مثال شروع میکنیم. فرض کنید به عنوان بخشی از راه حل یک مسئله بزرگ، لازم است محیط و مساحت یک سری چهارضلعی را محاسبه کنیم و قصد داریم این وظیفه را به طور کامل بر عهده قطعه کدهای مستقلی در برنامه قرار دهیم. به عبارت دیگر قصد داریم متناظر با هر یک از چهارضلعیهای موجود در مسئله یک شیء در برنامه داشته باشیم که قادر است محیط و مساحت خود را محاسبه و ارائه نماید. کلاس زیر که با زبان سی شارپ نوشته شده امکان ایجاد اشیاء مورد نظر را فراهم میکند.public class Rectangle { public double Width; public double Height; public double Area() { return Width*Height; } public double Perimeter() { return 2*(Width + Height); } }
در این قطعه برنامه نکات زیر قابل توجه است:
- کلاس با کلمه کلیدی class تعریف میشود.
- همان طور که مشاهده میکنید تعریف کلاس با کلمه public آغاز شده است. این کلمه محدوده دسترسی به کلاس را تعیین میکند. در اینجا از کلمه public استفاده کردیم تا بخشهای دیگر برنامه امکان استفاده از این کلاس را داشته باشند.
- پس از کلمه کلیدی class نوبت به نام کلاس میرسد. اگرچه انتخاب نام مورد نظر امری اختیاری است اما در آینده حتماً اصول و قراردادهای نامگذاری در داتنت را مطالعه نمایید. در حال حاضر حداقل به خاطر داشته باشید تا انتخاب نامی مناسب که گویای کاربرد کلاس باشد بسیار مهم است.
- باقیمانده کد، بدنه کلاس را تشکیل میدهد. جاییکه ویژگی ها، رفتارها و ... یا به طور کلی اعضای کلاس تعریف میشوند.
ایجاد شیء از یک کلاس و نحوه دسترسی به شیء ایجاد شده
شیء و کلاس چیزهای متفاوتی هستند. یک کلاس نوع یک شیء را تعریف میکند. اما یک شیء یک موجودیت عینی و واقعی بر اساس یک کلاس است. در اصطلاح از شیء به عنوان یک نمونه (Instance) یا وهله ای از کلاس مربوطه یاد میکنیم. همچنین به عمل ساخت شیء نمونه سازی یا وهله سازی گوییم.برای ایجاد شیء از کلمه کلیدی new و به دنبال آن نام کلاسی که قصد داریم بر اساس آن یک شیء بسازیم استفاده میکنیم. همان طور که اشاره شد کلاس یک نوع را تعریف میکند. پس از آن میتوان همانند سایر انواع مانند int, string, … برای تعریف متغیر استفاده نمود. به مثال زیر توجه کنید.
Rectangle rectangle = new Rectangle();
Rectangle rectangle;
Rectangle rectangle1 = new Rectangle(); Rectangle rectangle2 = rectangle1;
حالا میتوان شیء ساخته شده را با استفاده از ارجاعی که به آن داریم به کار برد.
Rectangle rectangle = new Rectangle(); rectangle.Width = 10.5; rectangle.Height = 10; double a = rectangle.Area();
فیلدها
اگر به تعریف کلاس دقت کنید مشخص است که دو متغییر Width و Height را با سطح دسترسی عمومی تعریف کرده ایم.به متغیرهایی از هر نوع که مستقیماً درون کلاس تعریف شوند (و نه مثلاً داخل یک تابع درون کلاس) فیلد میگوییم. فیلدها از اعضای کلاس دربردارنده آنها محسوب میشوند.
تعریف فیلدها مستقیماً در بدنه کلاس با یک Access Modifier شروع میشود و به دنبال آن نوع فیلد و سپس نام دلخواه برای فیلد میآید.
تذکر: نامگذاری مناسب یکی از مهمترین اصولی است که یک برنامه نویس باید همواره به آن توجه کافی داشته باشد و به شدت در بالا رفتن کیفیت برنامه موثر است. به خاطر داشته باشید تنها اجرا شدن و کار کردن یک برنامه کافی نیست. رعایت بسیاری از اصول مهندسی نرم افزار که ممکن است نقش مستقیمی در کارکرد برنامه نداشته باشند موجب سهولت در نگهداری و توسعه برنامه شده و به همان اندازه کارکرد صحیح برنامه مهم هستند. بنابراین مجدداً شما را دعوت به خواندن مقاله یاد شده بالا در مورد اصول نامگذاری صحیح میکنم. هر مفهوم تازه ای که میآموزید میتوانید به اصول نامگذاری همان مورد در مقاله پیش گفته مراجعه نمایید. همچنین افزونه هایی برای Visual Studio وجود دارد که شما را در زمینه نامگذاری صحیح و بسیاری موارد دیگر هدایت میکنند که یکی از مهمترین آنها Resharper نام دارد.
مثال:
// public field (Generally not recommended.) public double Width;
فیلدها معمولاً با سطح دسترسی خصوصی و برای نگهداری از دادههایی که مورد نیاز بیش از یک متد (یا تابع) درون کلاس است و آن دادهها باید پس از خاتمه کار یک متد همچنان باقی بمانند استفاده میشود. بدیهی است در غیر اینصورت به جای تعریف فیلد میتوان از متغیرهای محلی (متغیری که درون خود تابع تعریف میشود) استفاده نمود.
همان طور که پیشتر اشاره شد برای دسترسی به یک فیلد ابتدا یک نقطه پس از نام شیء درج کرده و سپس نام فیلد مورد نظر را مینویسیم.
Rectangle rectangle = new Rectangle(); rectangle.Width = 10.5;
public class Rectangle { public double Width = 5; // ... }
متدها
متدها قطعه کدهایی شامل یک سری دستور هستند. این مجموعه دستورات با فراخوانی متد و تعیین آرگومانهای مورد نیاز اجرا میشوند. در زبان سی شارپ به نوعی تمام دستورات در داخل متدها اجرا میشوند. در این زبان تمامی توابع در داخل کلاسها تعریف میشوند و بنابراین همه متد هستند.متدها نیز مانند فیلدها در داخل کلاس تعریف میشوند. ابتدا یک Access Modifier سطح دسترسی را تعیین مینماید. سپس به ترتیب نوع خروجی، نام متد و لیست پارامترهای آن در صورت وجود درج میشود. به مجموعه بخشهای یاد شده امضای متد میگویند.
پارامترهای یک متد داخل یک جفت پرانتز قرار میگیرند و با کاما (,) از هم جدا میشوند. یک جفت پرانتز خالی نشان دهنده آن است که متد نیاز به هیچ پارامتری ندارد.
بار دیگر به بخش تعریف متدهای کلاسی که ایجاد کردیم توجه نمایید.
public class Rectangle { // ... public double Area() { return Width*Height; } public double Perimeter() { return 2*(Width + Height); } }
همچنین توجه نمایید این شیء برای محاسبه مساحت و محیط خود نگاهی به ویژگیهای خود یعنی عرض و ارتفاعش که در فیلدهای آن نگهداری میکنیم میاندازد.
فراخوانی متد یک شیء همانند دسترسی به فیلد آن است. ابتدا نام شیء سپس یک نقطه و به دنبال آن نام متد مورد نظر به همراه پرانترها. آرگومانهای مورد نیاز در صورت وجود داخل پرانتزها قرار میگیرند و با کاما از هم جدا میشوند. که البته در این مثال متد ما نیازی به آرگومان ندارد. به همین دلیل برای فراخوانی آن تنها یک جفت پرانتز خالی قرار میدهیم.
در این بخش به دو مفهوم پارامتر و آرگومان اشاره شد. تفاورت آنها چیست؟
در هنگام تعریف یک متد نام و نوع پارامترهای مورد نیاز را تعیین و درج مینماییم. حال وقتی قصد فراخوانی متد را داریم باید مقادیر واقعی که آرگومان نامیده میشود را برای هر یک از پارامترهای تعریف شده فراهم نماییم. نوع آرگومان باید با نوع پارامتر تعریف شده تطبیق داشته باشد. اما اگر یک متغیر را به عنوان آرگومان در هنگام فراخوانی متد استفاده میکنیم نیازی به یکسان بودن نام آن متغیر و نام پارامتر تعریف شده نیست.
متدها میتوانند یک مقدار را به کدی که آن متد را فراخوانی کرده است بازگشت دهند.
Rectangle rectangle = new Rectangle(); rectangle.Width = 10.5; rectangle.Height = 10; double p = rectangle.Perimeter();
نکته: کلمه return علاوه بر بازگشت مقدار مورد نظر سبب پایان اجرای متد نیز میشود. حتی در صورتی که نوع خروجی یک متد void تعریف شده باشد استفاده از کلمه return بدون اینکه مقداری به دنبال آن بیاید میتواند برای پایان اجرای متد، در صورت نیاز و مثلاً برقراری شرطی خاص مفید باشد. بدون کلمه return متد زمانی پایان مییابد که به پایان قطعه کد بدنه خود برسد. توجه نمایید که در صورتی که نوع خروجی متد چیزی به جز void است استفاده از کلمه return به همراه مقدار مربوطه الزامی است.
مقدار خروجی یک متد را میتوان هر جایی که مقداری از همان نوع مناسب است مستقیماً به کار برد. همچنین میتوان آن را در یک متغیر قرار داد و سپس از آن استفاده نمود.
به عنوان مثال کلاس ساده زیر را در نظر بگیرید که متدی دارد برای جمع دو عدد.
public class SimpleMath { public int AddTwoNumbers(int number1, int number2) { return number1 + number2; } }
SimpleMath obj = new SimpleMath(); Console.WriteLine(obj.AddTwoNumbers(1, 2)); int result = obj.AddTwoNumbers(1, 2); Console.WriteLine(result);
در بخشهای بعدی بحث ما در مورد سایر اعضای کلاس و برخی جزییات پیرامون اعضای پیش گفته خواهد بود.