3. مبانی زبان C#
3.1. Introduction
ما در وهله اول C# را به عنوان یک زبان برنامهنویسی متعارف در نظر میگیریم. در مورد کلاسها بعداً صحبت خواهیم کرد. یک برنامه از دو بخش تشکیل شده است:
- دادهها
- دستورالعملهایی که آن را دستکاری میکنند
ما معمولاً سعی میکنیم دادهها را از دستورها جدا کنیم:
![]() |
3.2. دادهها در C#
سیشارپ از انواع داده زیر استفاده میکند:
- اعداد صحیح
- اعداد ممیز شناور
- اعداد اعشاری
- کاراکترها و رشتههای کاراکتری
- بولها
- ابجکتها
3.2.1. انواع داده از پیش تعریفشده
نوع C# | نوع .NET | دادههای نمایشدادهشده | پسوند از مقادیر صریح | کدگذاری | دامنه مقدار |
Char (S) | کاراکتر | ۲ بایت | نقطه کد یونیکد (UTF-16) | ||
رشته (C) | رشتهٔ کاراکتری | اشاره به دنبالهای از کاراکترهای یونیکد | |||
Integer (S) | صدم | ۴ بایت | [-231, 231-1] [–2147483648, 2147483647] | ||
UInt32 (S) | .. | U | ۴ بایت | [0, 232-1] [0, 4294967295] | |
Integer 64 بیتی (S) | .. | L | ۸ بایت | [-263, 263 -1] [–9223372036854775808, 9223372036854775807] | |
UInt64 (S) | .. | UL | ۸ بایت | [0, 264 -1] [0, 18446744073709551615] | |
.. | ۱ بایت | [-27 , 27 -1] [-128,+127] | |||
بایت(ها) | .. | 1 بایت | [0 , 28 -1] [0,255] | ||
Int16 (S) | .. | 2 بایت | [-215, 215-1] [-32768, 32767] | ||
UInt16 (S) | .. | 2 بایت | [0, 216-1] [0,65535] | ||
تکی (S) | عدد حقیقی | F | 4 بایت | [1.5 10-45, 3.4 10+38] (مقدار مطلق) | |
دوگانه (S) | .. | D | ۸ بایت | [-1.7 10+308, 1.7 10+308] در مقدار مطلق | |
ممیز (S) | عدد اعشاری | ام | ۱۶ بایت | [1.0 10-28,7.9 10+28] بهعنوان یک مقدار مطلق با ۲۸ رقم معنیدار | |
منطقی (S) | .. | ۱ بایت | true, false | ||
اشیاء (C) | ارجاع شیء | ارجاع شیء |
در بالا، ما انواع C# را به همراه انواع معادل .NET آنها فهرست کردهایم، با توضیح (S) اگر نوع یک ساختار باشد و (C) اگر نوع یک کلاس باشد. میتوانیم ببینیم که دو نوع ممکن برای یک عدد صحیح ۳۲ بیتی وجود دارد: int و Int32. نوع int یک نوع C# است. Int32 یک ساختار متعلق به فضای نام System است. بنابراین نام کامل آن System.Int32 است. نوع int یک نام مستعار C# است که به ساختار .NET System.Int32 اشاره میکند. به همین ترتیب، نوع C# string یک نام مستعار برای نوع .NET System.String است. System.String یک کلاس است، نه یک ساختار. این دو مفهوم مشابه هستند، اما با تفاوت اساسی زیر:
- یک متغیر از نوع Structure از طریق مقدار آن دستکاری میشود
- یک متغیر از نوع Classe از طریق نشانی آن (ارجاع در برنامهنویسی شیءگرا) دستکاری میشود.
هر دو structure و classe از انواع پیچیده با ویژگیها و متدها هستند. بنابراین، میتوان نوشت:
در مثال بالا، مقدار عددی 3 به طور پیشفرض از نوع C# int و در نتیجه از نوع .NET System.Int32 است. این ساختار دارای متدی به نام GetType() است که یک شیء را بازمیگرداند که ویژگیهای نوع داده System.Int32 را در خود جای داده است. در میان این ویژگیها، خاصیت FullName نام کامل نوع را بازمیگرداند. بنابراین میتوانیم ببینیم که مقدار عددی ۳، ابجکت پیچیدهتری از آن چیزی است که در نگاه اول به نظر میرسد.
در اینجا برنامهای آورده شده است که این نکات مختلف را نشان میدهد:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
//مثال ۱
int ent = 2;
float fl = 10.5F;
double d = -4.6;
string s = "essai";
uint ui = 5;
long l = 1000;
ulong ul = 1001;
byte octet = 5;
short sh = -4;
ushort ush = 10;
decimal dec = 10.67M;
bool b = true;
Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
}
}
}
- خط ۷: اعلان یک عدد صحیح ent
- خط ۱۹: نوع متغیر v را میتوان با استفاده از v.GetType().FullName بهدست آورد. اندازه ساختار S را میتوان با استفاده از sizeof(S) بهدست آورد. دستور Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) متنی را که پارامتر اول آن است روی صفحه چاپ میکند، به گونهای که هر بار {i} با مقدار عبارت parami جایگزین میشود.
- خط ۲۲: از آنجا که نوع string نشاندهنده یک کلاس است نه یک ساختار، اپراتور sizeof قابل استفاده نیست.
در اینجا نتیجه اجرای کد آورده شده است:
خروجی به جای نامهای مستعار C#، انواع .NET را نمایش میدهد.
3.2.2. نشانهگذاری برای دادههای صریح
145, -7, 0xFF (هگزادسیمال) | |
100000L | |
۱۳۴٫۷۸۹، ۴۵×۱۰⁻¹⁸ (−۴۵ × ۱۰⁻¹⁸) | |
۱۳۴.789F, -45E-18F (-45 × 10⁻¹⁸) | |
100000M | |
'A', 'b' | |
"امروز" "c:\\chap1\\paragraph3" @"c:\chap1\paragraph3" | |
true, false | |
new DateTime(1954,10,13) (سال، ماه، روز) برای ۱۳/۱۰/۱۹۵۴ |
به دو رشتهٔ متنی توجه کنید: "c:\\chap1\\paragraph3" و @"c:\chap1\paragraph3". در رشتههای متنی، کاراکتر \ تفسیر میشود. بنابراین، «\n» نمایانگر کاراکتر پایان خط است و نه توالی دو کاراکتر \ و n. اگر میخواستید این توالی را داشته باشید، باید «\\n» را مینوشتید، که در آن توالی \\ به عنوان یک کاراکتر \ واحد تفسیر میشود. همچنین میتوانید برای رسیدن به همان نتیجه، @"\n" را بنویسید. دستورات نحوی @"text" ایجاب میکند که متن دقیقاً همانطور که نوشته شده است، در نظر گرفته شود. گاهی به این رشتهی متنی، رشتهی verbatim گفته میشود.
3.2.3. اعلام دادهها
3.2.3.1. نقش اعلانها
یک برنامه با دادههایی کار میکند که با یک نام و یک نوع مشخص میشوند. این دادهها در حافظه ذخیره میشوند. هنگامی که برنامه کامپایل میشود، کامپایلر برای هر قطعه داده یک مکان حافظه را مشخص میکند که با یک آدرس و یک اندازه مشخص میشود. این کار را با استفاده از اعلانهایی که توسط برنامهنویس انجام شده است، انجام میدهد.
علاوه بر این، این اعلانها به کامپایلر امکان میدهند تا خطاهای برنامهنویسی را تشخیص دهد. برای مثال، عملگر
x = x * 2;
در صورتی که، برای مثال، x یک رشتهٔ کاراکتری باشد، این عملیات به عنوان یک خطا علامتگذاری خواهد شد.
3.2.3.2. اعلام ثابتها
نحوه اعلام یک مقدار ثابت به شرح زیر است:
برای مثال:
چرا ثابتها را تعریف کنیم؟
- خوانایی برنامه با دادن نام معنادار به ثابت آسانتر میشود:
- اصلاح برنامه در صورت نیاز به تغییر «ثابت» آسانتر خواهد بود. بنابراین، در مثال قبلی، اگر نرخ مالیات بر ارزش افزوده به ۳۳ درصد افزایش یابد، تنها تغییری که لازم است اعمال شود، اصلاح عبارت تعریفکننده مقدار آن است:
اگر 0.186 بهطور صریح در برنامه استفاده شده بود، در آن صورت لازم بود شمار زیادی از دستورات اصلاح شوند.
3.2.3.3. اعلام متغیر
یک متغیر با یک نام مشخص میشود و با یک نوع داده مرتبط است. C# بین حروف بزرگ و کوچک تمایز قائل میشود. بنابراین، متغیرهای FIN و fin با هم متفاوت هستند.
متغیرها را میتوان هنگام تعریف مقداردهی اولیه کرد. نحو تعریف یک یا چند متغیر به صورت زیر است:
که در آن Identificateur_de_type میتواند یک نوع از پیش تعریفشده یا نوعی باشد که توسط برنامهنویس تعریف شده است. به طور اختیاری، یک متغیر میتواند همزمان با تعریف شدن مقداردهی اولیه شود.
همچنین میتوان با استفاده از کلمه کلیدی var به جای Identificateur\_de\_type، نوع دقیق یک متغیر را حذف کرد:
کلمه کلیدی var** به این معنا نیست که متغیرها نوع مشخصی ندارند. متغیر variablei دارای نوع دادهای است که به آن تخصیص داده شده است (valeuri). مقداردهی اولیه در اینجا اجباری** است تا کامپایلر بتواند نوع متغیر را استنتاج کند.
در اینجا یک مثال آورده شده است:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
int i=2;
Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
var j = 3;
Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
var aujourdhui = DateTime.Now;
Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
}
}
}
- خط ۶: یک آیتم داده با نوع صریح
- خط ۷: (data).GetType().Name نام کوتاه برای (data) است، (data).GetType().FullName نام کامل برای (data) است.
- خط ۸: یک آیتم داده با نوع ضمنی. از آنجا که ۳ از نوع int است، j از نوع int خواهد بود.
- خط ۱۰: یک آیتم داده با نوع ضمنی. از آنجا که DateTime.Now از نوع DateTime است، aujourdhui از نوع DateTime خواهد بود.
در زمان اجرا، نتیجه زیر به دست میآید:
یک متغیر که بهطور ضمنی با کلیدواژه var نوعدهی شده است، نمیتواند بعداً نوع خود را تغییر دهد. بنابراین، پس از خط ۱۰ کد، نمیتوان خط زیر را نوشت:
var aujourdhui = "aujourd'hui";
بعداً خواهیم دید که میتوان یک نوع را بهصورت «آنلاینی» درون یک عبارت تعریف کرد. این نوع، نوع ناشناس نامیده میشود – نوعی که کاربر برای آن نامی انتخاب نکرده است. کامپایلر است که برای این نوع جدید نامی تعیین میکند. اگر قرار باشد دادههای یک نوع ناشناس به یک متغیر اختصاص یابد، تنها راه تعریف آن متغیر استفاده از کلمهٔ کلیدی var است.
3.2.4. تبدیل بین اعداد و رشتهها
nombre.ToString() | |
int.Parse(string) یا System.Int32.Parse | |
long.Parse(string) یا System.Int64.Parse | |
double.Parse (رشته) یا System.Double.Parse (رشته) | |
float.Parse (رشته) یا System.Float.Parse (رشته) |
تبدیل یک رشته به عدد ممکن است ناموفق باشد اگر رشته نمایانگر عدد معتبری نباشد. در این صورت، یک خطای مرگبار به نام استثنا تولید میشود. این خطا را میتوان با بند زیر try/catch مدیریت کرد:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
اگر تابع خطا (exception) ایجاد نکند، برنامه به دستور بعدی ادامه میدهد؛ در غیر این صورت، وارد بدنه شرط catch میشود و سپس به دستور بعدی ادامه میدهد. بعداً به موضوع مدیریت خطا باز خواهیم گشت. در اینجا برنامهای آورده شده است که برخی از تکنیکهای تبدیل بین اعداد و رشتههای متنی را نشان میدهد. در این مثال، تابع affiche مقدار پارامتر خود را روی صفحه نمایش چاپ میکند. بنابراین، affiche(S) مقدار S را روی صفحه نمایش چاپ میکند، که S از نوع string است.
using System;
namespace Chap1 {
class P01 {
static void Main(string[] args) {
// دادهها
const int i = 10;
const long l = 100000;
const float f = 45.78F;
double d = -14.98;
// number --> string
affiche(i.ToString());
affiche(l.ToString());
affiche(f.ToString());
affiche(d.ToString());
//بولی → رشته
const bool b = false;
affiche(b.ToString());
// string --> int
int i1;
i1 = int.Parse("10");
affiche(i1.ToString());
try {
i1 = int.Parse("10.67");
affiche(i1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// string --> long
long l1;
l1 = long.Parse("100");
affiche(l1.ToString());
try {
l1 = long.Parse("10.675");
affiche(l1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// string --> double
double d1;
d1 = double.Parse("100,87");
affiche(d1.ToString());
try {
d1 = double.Parse("abcd");
affiche(d1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// string --> float
float f1;
f1 = float.Parse("100,87");
affiche(f1.ToString());
try {
d1 = float.Parse("abcd");
affiche(f1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
}// پایان main
public static void affiche(string S) {
Console.Out.WriteLine("S={0}",S);
}
}// پایان کلاس
}
خطوط ۳۰ تا ۳۲ هر استثنایی را که ممکن است رخ دهد، مدیریت میکنند. e.Message پیام خطایی است که با استثنای e مرتبط است.
نتایج بهدستآمده به شرح زیر است:
توجه داشته باشید که اعداد حقیقی نمایشدادهشده بهصورت رشتههای کاراکتری باید بهجای نقطه از ویرگول استفاده کنند. بنابراین، مینویسیم
اما
3.2.5. آرایههای داده
یک آرایه در C# ابجکتی است که امکان گروهبندی دادههای یکسان را تحت یک شناسه واحد فراهم میکند. این آرایه به صورت زیر تعریف میشود:
n تعداد آیتمهای دادهای است که آرایه میتواند در خود جای دهد. سینتکس Array[i] به آیتم دادهای با شماره i اشاره دارد، که در آن i به بازه [0,n-1] تعلق دارد. هرگونه ارجاع به آیتم داده Tableau[i] که i به بازه [0,n-1] تعلق نداشته باشد، باعث ایجاد یک استثنا خواهد شد. یک آرایه میتواند همزمان با تعریف شدن، مقداردهی اولیه شود:
یا، به سادگی:
آرایهها دارای خاصیت **Length** هستند که تعداد عناصر آرایه را نشان میدهد.
یک آرایه دو بعدی را میتوان به صورت زیر تعریف کرد:
Type[,] array = new Type[n,m];
که در آن n تعداد سطرها و m تعداد ستونها است. عبارت Tableau[i,j] به عنصر j در سطر i آرایه اشاره دارد. آرایه دوبعدی همچنین میتواند همزمان با تعریف شدن مقداردهی اولیه شود:
یا، به سادگی:
تعداد عناصر در هر بعد را میتوان با استفاده از روش GetLength(i) بهدست آورد، که در آن i=0 نمایانگر بُعد مربوط به شاخص اول، i=1 نمایانگر بُعد مربوط به شاخص دوم و … است.
تعداد کل ابعاد با استفاده از خاصیت Rank و تعداد کل عناصر با استفاده از خاصیت Length به دست میآید.
یک آرایه از آرایهها به صورت زیر تعریف میشود:
Type[][] array = new Type[n][];
اعلامیهی بالا یک آرایه با n سطر ایجاد میکند. هر عنصر array[i] یک مرجع آرایهی یکبعدی است. این مراجع array[i] در اعلامیهی بالا مقداردهی اولیه نشدهاند. مقدار آنها مرجع null است.
مثال زیر ایجاد یک آرایه از آرایهها را نشان میدهد:
// یک آرایه از آرایهها
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//برای
// ابتداییسازی
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//برای j
}//برای i
- خط ۲: یک آرایه noms شامل ۳ عنصر از نوع string[][]. هر عنصر یک نشانگر آرایه (یک مرجع شیء) است که عناصر آن از نوع string[] هستند.
- خطوط ۳–۵: سه عنصر آرایه noms مقداردهی اولیه میشوند. هر یک اکنون به آرایهای از عناصر از نوع string[] اشاره میکند. `noms[i][j]` عنصر `j` از آرایه `string[]` است که توسط `noms[i]` ارجاع داده شده است.
- خط ۹: inicialization عنصر `noms[i][j]` در یک حلقهٔ تو در تو. در اینجا `noms[i]` آرایهای از `i+1` عنصر است. از آنجا که `noms[i]` یک آرایه است، `noms[i].Length` تعداد عناصر آن را نشان میدهد.
در اینجا مثالی از ترکیب سه نوع آرایهای که به تازگی در موردشان بحث کردیم آورده شده است:
using System;
namespace Chap1 {
// آرایهها
using System;
//کلاس آزمایشی
public class P02 {
public static void Main() {
//یک آرایه یکبعدی مقداردهی اولیه شده
int[] entiers = new int[] { 0, 10, 20, 30 };
for (int i = 0; i < entiers.Length; i++) {
Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
}//برای
//یک آرایهٔ دوبعدی که مقداردهی اولیه شده است
double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
for (int i = 0; i < réels.GetLength(0); i++) {
for (int j = 0; j < réels.GetLength(1); j++) {
Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
}//برای j
}//برای i
//آرایهای از آرایهها
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//برای
// ابتداییسازی
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//برای j
}//برای i
//نمایش
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
}//برای j
}//برای i
}//اصلی
}//کلاس
}//فضای نام
هنگام اجرا، نتایج زیر را به دست میآوریم:
3.3. دستورهای پایهای C#
ما بین
1 دستورات پایهای که توسط کامپیوتر اجرا میشوند.
۲ دستورالعملهایی که جریان برنامه را کنترل میکنند.
دستورات پایه زمانی روشن میشوند که ساختار یک ریزرایانه و لوازم جانبی آن را در نظر بگیریم.
![]() |
-
خواندن اطلاعات از صفحهکلید
-
پردازش اطلاعات
-
نوشتن اطلاعات روی صفحه نمایش
3.3.1. نوشتن روی صفحه
دستورهای مختلفی برای نوشتن روی صفحه وجود دارد:
Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)
که در آن expression هر نوع دادهای است که میتوان آن را به یک رشته کاراکتری برای نمایش روی صفحه تبدیل کرد. تمام اشیاء C# یا .NET دارای متد ToString() هستند که برای انجام این تبدیل استفاده میشود.
کلاس System.Console دسترسی به عملیات نوشتن روی صفحه (Write, WriteLine) را فراهم میکند. کلاس Console دارای دو خاصیت Out و Error است که جریانهای نوشتن از نوع TextWriter هستند:
- Console.WriteLine() معادل Console.Out.WriteLine() است و به جریان Out که معمولاً با صفحه مرتبط است، مینویسد.
- تابع Console.Error.WriteLine() به جریان خطا (Error stream) مینویسد که معمولاً با صفحه نمایش نیز مرتبط است.
جریانهای Out و Error را میتوان در زمان اجرا به فایلهای متنی هدایت کرد، همانطور که بهزودی خواهیم دید.
3.3.2. خواندن دادههای تایپشده روی صفحهکلید
جریان داده از صفحهکلید توسط شیء Console.In از نوع TextReader نمایش داده میشود. این نوع شیء امکان خواندن یک خط متن را با استفاده از متد ReadLine فراهم میکند:
کلاس Console متدی به نام ReadLine را فراهم میکند که به طور پیشفرض با استریم In مرتبط است. بنابراین میتوانیم بنویسیم:
خطی که روی صفحهکلید تایپ میشود در متغیر ligne ذخیره شده و سپس میتواند توسط برنامه پردازش شود. جریان ورودی (In) میتواند درست مانند جریانهای خروجی (Out) و خطا (Error) به یک فایل هدایت شود.
3.3.3. نمونهای از ورودی/خروجی
در اینجا یک برنامه کوتاه برای نمایش عملیات ورودی و خروجی از طریق صفحهکلید/صفحه نمایش آورده شده است:
using System;
namespace Chap1 {
//کلاس آزمایشی
public class P03 {
public static void Main() {
// نوشتن به جریان خروجی
object obj = new object();
Console.Out.WriteLine(obj);
//نوشتن در جریان خطا
int i = 10;
Console.Error.WriteLine("i=" + i);
// خواندن یک خط واردشده از طریق صفحهکلید
Console.Write("Tapez une ligne : ");
string ligne = Console.ReadLine();
Console.WriteLine("ligne={0}", ligne);
}//پایان تابع اصلی
}//پایان کلاس
}
- خط ۹: obj یک مرجع شیء است
- خط ۱۰: obj روی صفحه نمایش نوشته میشود. به طور پیشفرض، متد obj.ToString() فراخوانی میشود.
- خط ۱۴: شما همچنین میتوانید بنویسید:
Console.Error.WriteLine("i={0}",i);
پارامتر اول، «i={0}»، قالب نمایش است؛ سایر پارامترها عبارتهایی هستند که باید نمایش داده شوند. عناصر {n} پارامترهای «موقعیتی» هستند. در زمان اجرا، پارامتر {n} با مقدار عبارت n ام جایگزین میشود.
نتیجهٔ اجرا به شرح زیر است:
- خط ۱: خروجی تولید شده توسط خط ۱۰ کد. متد obj.ToString() نام نوع متغیر obj را نمایش داد: System.Object. نوع object یک نام مستعار C# برای نوع .NET System.Object است.
3.3.4. انحراف ورودی/خروجی
در DOS و UNIX، سه دستگاه استاندارد به نامهای زیر وجود دارند:
- دستگاه ورودی استاندارد – بهطور پیشفرض به صفحهکلید اشاره دارد و شمارهٔ آن 0 است
- دستگاه خروجی استاندارد – بهطور پیشفرض به صفحه نمایش اشاره دارد و شمارهٔ آن ۱ است
- دستگاه خطای استاندارد – بهطور پیشفرض روی صفحه نمایش است و شمارهٔ ۲ را دارد
در C#، جریان خروجی Console.Out در دستگاه ۱ مینویسد، جریان خروجی Console.Error در دستگاه ۲ مینویسد و جریان ورودی Console.In از دستگاه ۰ داده میخواند.
هنگام اجرای یک برنامه تحت DOS یا یونیکس، میتوانید مشخص کنید که کدام دستگاهها برای برنامه در حال اجرا برابر 0، 1 و 2 باشند. خط فرمان زیر را در نظر بگیرید:
پس از آرگومانهای argi برنامه pg، دستگاههای ورودی/خروجی استاندارد را میتوان به فایلها هدایت کرد:
جریان ورودی استاندارد شماره ۰ به فایل in.txt هدایت میشود. بنابراین در برنامه، جریان Console.In دادههای خود را از فایل in.txt خواهد گرفت. | |
خروجی شماره ۱ را به فایل out.txt هدایت میکند. در نتیجه، در داخل برنامه، جریان Console.Out دادههای خود را به فایل out.txt خواهد نوشت. | |
همین امر صدق میکند، اما دادههای نوشته شده به محتویات فعلی فایل out.txt ضمیمه میشوند. | |
خروجی شمارهٔ ۲ را به فایل error.txt هدایت میکند. در نتیجه، در داخل برنامه، جریان Console.Error دادههای خود را به فایل error.txt خواهد نوشت. | |
همین امر صدق میکند، اما دادههای نوشته شده به محتویات فعلی فایل error.txt ضمیمه میشوند. | |
دستگاههای ۱ و ۲ هر دو به فایلها هدایت شدهاند |
توجه داشته باشید که برای هدایت جریانهای ورودی/خروجی برنامه pg به فایلها، نیازی به تغییر برنامه pg نیست. این سیستمعامل است که ماهیت دستگاههای 0، 1 و 2 را تعیین میکند. برنامه زیر را در نظر بگیرید:
using System;
namespace Chap1 {
// سوئیچ میکند
public class P04 {
public static void Main(string[] args) {
// خواندن از جریان ورودی
string data = Console.In.ReadLine();
//نوشتن در جریان خروجی
Console.Out.WriteLine("écriture dans flux Out : " + data);
//نوشتن به جریان خطا
Console.Error.WriteLine("écriture dans flux Error : " + data);
}//اصلی
}//کلاس
}
بیایید فایل اجرایی را از این کد منبع تولید کنیم:
![]() |
- به عنوان [1]: فایل اجرایی با کلیک راست روی پروژه / Build ایجاد میشود.
- همانطور که در [2] مشاهده میشود: در یک پنجره DOS، فایل اجرایی 04.exe در پوشه پروژه bin/Release ایجاد شده است.
بیایید دستورات زیر را در پنجره DOS [2] وارد کنیم:
- خط ۱: رشته test در فایل in.txt نوشته میشود
- خطوط ۲–۳: نمایش محتویات فایل in.txt برای تأیید
- خط ۴: برنامه 04.exe را اجرا کنید. جریان In به فایل in.txt هدایت میشود، جریان Out به فایل out.txt و جریان Error به فایل err.txt هدایت میشود. اجرا هیچ خروجیای ندارد.
- خطوط ۵–۶: محتویات فایل out.txt. این محتویات به ما نشان میدهد که:
- فایل in.txt خوانده شده است
- خروجی صفحه به out.txt هدایت شده است
- خطوط ۷–۸: تأیید مشابه برای فایل err.txt
واضح است که جریانهای Out و In به دستگاههای یکسان نمینویسند، زیرا میتوان آنها را بهطور جداگانه هدایت کرد.
3.3.5. مختص کردن مقدار یک عبارت به یک متغیر
ما در اینجا به عملیات variable=expression; میپردازیم
این عبارت میتواند از انواع زیر باشد: عددی، مقایسهای، بولی، رشتهای
3.3.5.1. تفسیر عمل تخصیص
عملیات variable=expression;
خود یک عبارت است که ارزیابی آن به شرح زیر انجام میشود:
- سمت راست عملیات تخصیص ارزیابی میشود: نتیجه یک مقدار V است.
- مقدار V به متغیر اختصاص داده میشود
- مقدار V همچنین مقدار عملیات تخصیص است، این بار به عنوان یک عبارت در نظر گرفته میشود.
اینگونه عمل
صحیح است. به دلیل اولویت عملگرها، عملگر = در سمت راست ارزیابی میشود. بنابراین داریم
بیان V2=expression ارزیابی شده و مقدار V را دارد. ارزیابی این بیان باعث تخصیص مقدار V2 به V شد. سپس عملگر بعدی «=» به صورت زیر ارزیابی میشود:
مقدار این عبارت همچنان V است. ارزیابی آن منجر به تخصیص مقدار V به V1 میشود.
بنابراین، عمل V1=V2=expression
یک عبارت است که ارزیابی آن
- که در نتیجه، مقدار expression به متغیرهای V1 و V2 تخصیص داده میشود
- مقدار expression را برمیگرداند.
این را میتوان به صورت کلی به عبارتی با شکل زیر تعمیم داد:
3.3.5.2. بیان عددی
عملگرهای مورد استفاده در عبارتهای حسابداری به شرح زیر هستند:
-
جمع
-
تفریق
* ضرب
/ تقسیم: نتیجه، حاصلتقسیم دقیق است اگر حداقل یکی از عملوندها عدد حقیقی باشد. اگر هر دو عملوند عدد صحیح باشند، نتیجه، حاصلتقسیم صحیح است. بنابراین 5/2 → 2 و 5.0/2 → 2.5.
% تقسیم: نتیجه باقیمانده است، صرفنظر از ماهیت عملوندها، که در آن کسر یک عدد صحیح است. این عمل، عمل ماژولو است.
توابع ریاضی مختلفی وجود دارند. در اینجا به چند نمونه از آنها اشاره میشود:
جذر | |
کسینوس | |
سینوس | |
تانژنت | |
x به توان y (x>0) | |
نرخ نمایی | |
لگاریتم طبیعی | |
مقدار مطلق |
و غیره...
تمام این توابع در کلاسی به نام Math در C# تعریف شدهاند. هنگام استفاده از آنها، باید نام کلاس تعریفکننده را قبل از نام تابع قرار دهید. بنابراین، به این صورت مینویسید:
تعریف کامل کلاس Math به شرح زیر است:





3.3.5.3. ترتیب اولویت در ارزیابی عبارتهای عددی
ترتیب اولویت عملگرها هنگام ارزیابی عبارتهای عددی به شرح زیر است (از بالاترین به پایینترین):
عملگرها در یک بلوک [ ] اولویت یکسانی دارند.
3.3.5.4. بیانهای مقایسهای
عملگرها به شرح زیر هستند:
اولویت عملگرها
نتیجه یک عبارت رابطهای مقدار بولین false در صورتی که عبارت نادرست باشد، و true در غیر این صورت است.
مقایسه دو کاراکتر
فرض کنید دو کاراکتر C1 و C2 وجود داشته باشند. آنها را میتوان با استفاده از عملگرها مقایسه کرد
در واقع کدهای یونیکد آنها – که عدد هستند – با هم مقایسه میشوند. بر اساس ترتیب یونیکد، روابط زیر برقرار است:
مقایسه دو رشته کاراکتری
آنها کاراکتر به کاراکتر مقایسه میشوند. اولین نابرابری که بین دو کاراکتر یافت میشود، منجر به نابرابریای با همان علامت برای رشتهها میشود.
مثالها:
مقایسهٔ رشتههای «Cat» و «Dog» را در نظر بگیرید.
![]() |
این اختلاف نهایی به این معنی است که «Cat» < «Dog».
فرض کنید رشتههای «Cat» و «Kitten» را با هم مقایسه میکنیم. آنها تا زمانی که رشته «Cat» به پایان نرسد، برابر هستند. در این حالت، رشته تمامشده بهعنوان «کوچکتر» اعلام میشود. بنابراین رابطه زیر برقرار است:
تابعهای مقایسه دو رشته
میتوانید از عملگرهای مقایسهای == و != برای بررسی اینکه آیا دو رشته برابر هستند یا خیر استفاده کنید، یا از متد Equals کلاس System.String استفاده کنید. برای روابط < <= > >=، باید از متد <span id="stringcompareto" name="stringcompareto" class="odt-bookmark-anchor"></span> کلاس System.String استفاده کنید:
using System;
namespace Chap1 {
class P05 {
static void Main(string[] args) {
string chaine1="chat", chaine2="chien";
int n = chaine1.CompareTo(chaine2);
bool egal = chaine1.Equals(chaine2);
Console.WriteLine("i={0}, egal={1}", n, egal);
Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
}
}
}
در خط ۷، متغیر i مقدار زیر را خواهد داشت:
0 اگر دو رشته برابر باشند
1 اگر رشته شماره ۱ > رشته شماره ۲
-1 اگر رشته ۱ < رشته ۲
خط ۸: متغیر egal مقدار true را در صورتی که دو رشته برابر باشند خواهد داشت و در غیر این صورت مقدار false را. خط ۱۰: عملگرهای == و != برای بررسی برابری یا عدم برابری دو رشته استفاده میشوند.
نتایج اجرای برنامه:
3.3.5.5. بیانهای بولی
عملگرهایی که میتوان استفاده کرد عبارتند از AND (&&) OR (||) NOT (!). نتیجه یک عبارت بولی یک مقدار بولی است.
اولویت عملگرها:
- !
- ||
- ||
double x = 3.5;
bool valide = x > 2 && x < 4;
عملگرهای رابطی بر عملگرهای && و || اولویت دارند.
3.3.5.6. عملیات بیتبیتی
عملگرها
فرض کنید i و j دو عدد صحیح باشند.
i را n بیت به سمت چپ جابجا میکند. بیتهای ورودی صفر هستند. | |
i را n بیت به سمت راست جابجا میکند. اگر i یک عدد صحیح علامتدار (signed char, int, long) باشد، بیت علامت حفظ میشود. | |
یک عملیات OR منطقی بیتبهبیت بین i و j انجام میدهد. | |
یک عملیات منطقی OR بیت به بیت روی i و j انجام میدهد (OU). | |
مکملگیری i به 1 | |
عملیات OU EXCLUSIF را روی i و j انجام میدهد |
کد زیر را در نظر بگیرید:
short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
- قالب {0:x4} پارامتر شماره 0 را در قالب هگزادسیمال (x) با استفاده از 4 کاراکتر (4) نمایش میدهد.
نتایج اجرای کد به شرح زیر است:
3.3.5.7. ترکیب عملگرها
a=a+b را میتوان به صورت a+=b نوشت
a=a-b را میتوان به صورت a-=b نوشت
همین امر در مورد عملگرهای /، %، *، <<، >>، &، | و ^ نیز صدق میکند. بنابراین، a=a/2; را میتوان به صورت a/=2; نوشت.
3.3.5.8. عملگرهای افزایش و کاهش
علامت variable++ به معنای variable=variable+1 یا variable+=1 است
نشان variable-- به معنای variable=variable-1 یا variable-=1 است.
3.3.5.9. عملگر سهتایی؟
بیان
به شرح زیر محاسبه میشود:
۱ بیان expr_cond ارزیابی میشود. این یک بیان شرطی است که مقدار آن میتواند vrai یا faux باشد.
2 اگر این عبارت درست باشد، مقدار آن برابر با expr1 است و expr2 ارزیابی نمیشود.
3 اگر این عبارت نادرست باشد، عکس این عمل رخ میدهد: مقدار عبارت برابر با مقدار expr2 است و expr1 ارزیابی نمیشود.
عملیات i=(j>4 ? j+1:j-1); مقدار j+1 را به متغیر i تخصیص میدهد اگر j>4، در غیر این صورت j-1. این معادل نوشتن if(j>4) i=j+1; else i=j-1; است، اما مختصرتر است.
3.3.5.10. اولویتبندی کلی عملگرها
gd | |
dg | |
dg | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
dg | |
dg |
gd نشان میدهد که در مواردی که عملگرها اولویت یکسان دارند، اولویت از چپ به راست رعایت میشود. این بدان معناست که وقتی یک عبارت شامل عملگرهایی با اولویت یکسان باشد، عملگری که در سمت چپتر قرار دارد، ابتدا ارزیابی میشود. dg نشاندهنده اولویت از راست به چپ است.
3.3.5.11. تبدیل نوع
امکان دارد در داخل یک عبارت، نمایش یک مقدار را بهطور موقت تغییر داد. این عمل «تبدیل نوع» (type casting) نامیده میشود. نحوهی تغییر نوع یک مقدار در داخل یک عبارت به شرح زیر است:
سپس مقدار، نوع مشخصشده را به خود میگیرد. این امر منجر به تغییر در نمایش مقدار میشود.
using System;
namespace Chap1 {
class P06 {
static void Main(string[] args) {
int i = 3, j = 4;
float f1=i/j;
float f2=(float)i/j;
Console.WriteLine("f1={0}, f2={1}",f1,f2);
}
}
}
- در خط ۷، f1 مقدار 0.0 را خواهد داشت. تقسیم ۳/۴ یک تقسیم صحیح است زیرا هر دو عملوند از نوع int هستند.
- خط ۸: (float)i مقدار i است که به float تبدیل شده است. اکنون، ما یک تقسیم بین یک عدد حقیقی از نوع float و یک عدد صحیح از نوع int داریم. این تقسیم بین اعداد حقیقی است که سپس انجام میشود. مقدار j نیز به نوع float تبدیل خواهد شد، و سپس تقسیم دو عدد حقیقی انجام میشود. در این صورت f2 مقدار 0.75 را خواهد داشت.
در اینجا نتایج اجرا آورده شده است:
در عملیات (float)i:
- i مقداری است که دقیقاً روی ۲ بایت رمزگذاری شده است
- (float) i همان مقدار است که به صورت یک عدد ممیز شناور ۴ بایتی نزدیکسازی شده است
بنابراین مقدار i تبدیلکد میشود. این تبدیلکد تنها در طول یک محاسبه انجام میشود؛ متغیر i همیشه نوع int خود را حفظ میکند.
3.4. دستورالعملهای کنترل جریان برنامه
3.4.1. توقف
متد Exit که در کلاس Environment تعریف شده است، امکان توقف اجرای یک برنامه را فراهم میکند.
syntaxe void Exit(int status)
action arrête le processus en cours et rend la valeur status au processus père
Exit فرآیند جاری را خاتمه میدهد و کنترل را به فرآیند فراخواننده بازمیگرداند. مقدار status میتواند توسط فرآیند فراخوانیکننده استفاده شود. در DOS، این متغیر وضعیت در متغیر سیستمی ERRORLEVEL بازگردانده میشود که مقدار آن را میتوان در یک فایل دستهای بررسی کرد. در یونیکس، با شل بورن، متغیر $? مقدار status را در خود نگه میدارد.
برنامه را با مقدار وضعیت 0 خاتمه میدهد.
3.4.2. ساختار تصمیمگیری ساده
یادداشتها:
- شرط در داخل پرانتز قرار دارد.
- هر عمل با یک نقطهویرگول خاتمه مییابد.
- کروشهها به دنبالهٔ نقطه ویرگول نیستند.
- کروشهها فقط در صورتی لازم هستند که بیش از یک عمل وجود داشته باشد.
- بند else را میتوان حذف کرد.
- هیچ بند then وجود ندارد.
معادل الگوریتمی این ساختار، ساختار «if … then … else» است:
![]() |
مثال
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
ساختارهای انتخابی میتوانند تو در تو باشند:
گاهی اوقات مشکل زیر پیش میآید:
using System;
namespace Chap1 {
class P07 {
static void Main(string[] args) {
int n = 5;
if (n > 1)
if (n > 6)
Console.Out.WriteLine(">6");
else Console.Out.WriteLine("<=6");
}
}
}
در مثال قبلی، if کدام یک از else در خط ۱۰ به آن اشاره میکند؟ قاعده این است که یک else همیشه به نزدیکترین if اشاره میکند: if(n>6), در خط ۸، در مثال. بیایید مثال دیگری را در نظر بگیریم:
if (n2 > 1) {
if (n2 > 6) Console.Out.WriteLine(">6");
} else Console.Out.WriteLine("<=1");
در اینجا ما میخواستیم یک else را به if(n2>1) اختصاص دهیم، و نه یک else را به if(n2>6). به دلیل نکتهٔ قبلی، مجبوریم از پرانتزهای منحنی استفاده کنیم: if(n2>1) {...} else ...
3.4.3. ساختار مورد
سینتکس به شرح زیر است:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default:
actions_sinon;
break;
}
یادداشتها
- مقدار عبارت چک برای switch میتواند یک عدد صحیح، یک کاراکتر یا یک رشته باشد
- بیان کنترل در داخل پرانتز قرار دارد.
- بند default ممکن است حذف شود.
- مقادیر vi مقادیر ممکن برای عبارت هستند. اگر عبارت به vi ارزیابی شود، عملیات پس از بند مورد vi اجرا میشوند.
- دستور break ساختار مورد را ترک میکند.
- هر بلوک دستورات مرتبط با مقداری از vi باید با یک دستور شاخهزنی (break، goto، return و غیره) پایان یابد؛ در غیر این صورت، کامپایلر خطا گزارش میکند.
مثال
در الگوریتمها
selon la valeur de choix
cas 0
fin du module
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
در سیشارپ
3.4.4. ساختارهای تکرار
3.4.4.1. تعداد تکرارهای معلوم
حلقه for
سینتکس به شرح زیر است:
for (i=id;i<=if;i=i+ip){
actions;
}
یادداشتها
- سه آرگومان for در پرانتز قرار گرفته و با نقطهویرگول از هم جدا شدهاند.
- هر عمل در for با یک نقطهویرگول دنبال میشود.
- کروشهها فقط در صورتی لازم هستند که بیش از یک عمل وجود داشته باشد.
- کروشه منحنی با نقطه ویرگول دنبال نمیشود.
معادل الگوریتمی آن ساختار pour است:
که میتواند به ساختار tantque تبدیل شود:
ساختار foreach
سینتکس به شرح زیر است:
foreach (Type variable in collection)
instructions;
}
یادداشتها
- collection یک مجموعه قابل شمارش از اشیاء است. مجموعهی قابل شمارش از اشیاء که با آن آشنا هستیم، آرایه است.
- Type نوع اشیاء موجود در مجموعه است. برای یک آرایه، این نوع، نوع عناصر آرایه خواهد بود.
- variable متغیری است که به چرخه محلی است و به طور متوالی تمام مقادیر مجموعه را به خود اختصاص میدهد.
بنابراین، کد زیر:
string[] amis = { "paul", "hélène", "jacques", "sylvie" };
foreach (string nom in amis) {
Console.WriteLine(nom);
}
نمایش داده میشود:
3.4.4.2. تعداد تکرارها نامشخص
در C# ساختارهای کنترل متعددی برای این سناریو وجود دارد.
حلقه while
while(condition){
actions;
}
حلقه تا زمانی که شرط برقرار باشد ادامه مییابد. ممکن است هرگز اجرا نشود.
نکات:
- شرط در داخل پرانتز قرار میگیرد.
- هر عمل با یک نقطهویرگول دنبال میشود.
- کروشهها فقط در صورتی لازم هستند که بیش از یک عمل وجود داشته باشد.
- کروشهٔ منحنی با نقطهویرگول دنبال نمیشود.
ساختار الگوریتمی متناظر، ساختار «while» است:
حلقه do-while
سینتکس به شرح زیر است:
do{
instructions;
}while(condition);
حلقه تا زمانی که شرط برقرار باشد ادامه مییابد. در این صورت، حلقه حداقل یک بار اجرا میشود.
یادداشتها
- شرط در داخل پرانتز قرار میگیرد.
- هر عمل با یک نقطهویرگول دنبال میشود.
- کروشهها فقط در صورتی لازم هستند که بیش از یک عمل وجود داشته باشد.
- کروشهٔ منحنی با نقطهویرگول دنبال نمیشود.
ساختار الگوریتمی متناظر، ساختار «تکرار … تا» است:
ساختار برای 'for'
سینتکس به شرح زیر است:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
حلقه تا زمانی که شرط برقرار باشد ادامه مییابد (که قبل از هر تکرار ارزیابی میشود). Instructions_départ قبل از ورود به حلقه برای اولین بار اجرا میشوند. Instructions_fin_boucle پس از هر تکرار اجرا میشوند.
یادداشتها
- دستورالعملهای مختلف در instructions_depart و instructions_fin_boucle با ویرگول از هم جدا شدهاند.
ساختار الگوریتمی متناظر به شرح زیر است:
مثالها
قطعات کد زیر همگی مجموع ۱۰ عدد صحیح اول را محاسبه میکنند.
int i, somme, n=10;
for (i = 1, somme = 0; i <= n; i = i + 1)
somme = somme + i;
for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;
i = 1; somme = 0;
while (i <= n) { somme += i; i++; }
i = 1; somme = 0;
do somme += i++;
while (i <= n);
3.4.4.3. دستورالعملهای مدیریت حلقه
خروج از حلقه for، while یا do...while. | |
به تکرار بعدی حلقههای for، while و do...while میرود |
3.5. مدیریت استثنا
بسیاری از توابع C# قادر به پرتاب استثناها، یعنی خطاها، هستند. وقتی یک تابع قادر به پرتاب استثنا باشد، برنامهنویس باید آن را مدیریت کند تا برنامههایی مقاومتر در برابر خطاها تولید کند: همیشه باید از «کرش» ناگهانی برنامه جلوگیری کرد.
استثناء به شرح زیر مدیریت میشود:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
اگر تابع استثنا پرتاب نکند، برنامه به دستور بعدی ادامه میدهد؛ در غیر این صورت، وارد بدنه بند catch شده و سپس به دستور بعدی ادامه میدهد. به نکات زیر توجه کنید:
- e یک شیء از نوع Exception یا یکی از انواع مشتقشده آن است. میتوانیم با استفاده از نوعهایی مانند IndexOutOfRangeException، FormatException، SystemException و غیره، مشخصتر عمل کنیم: چندین نوع استثنا وجود دارد. با نوشتن `catch (Exception e)`، نشان میدهیم که میخواهیم تمام انواع استثناءها را مدیریت کنیم. اگر کد در بند `try` احتمالاً چندین نوع استثناء تولید کند، ممکن است بخواهیم با مدیریت استثناء با چندین بند `catch`، مشخصتر عمل کنیم:
try{
code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
traiter l'exception e1
}
} catch ( FormatException e2){
traiter l'exception e2
}
instruction suivante
- یک بند «finally» میتواند به بندهای try/catch اضافه شود:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
finally{
code exécuté après try ou catch
}
instruction suivante
چه استثنایی وجود داشته باشد یا نداشته باشد، کد در بند finally همیشه اجرا خواهد شد.
- در بند catch، ممکن است نخواهید از شیء موجود Exception استفاده کنید. به جای نوشتن catch (Exception e){..}، در این صورت مینویسید catch(Exception){...} یا به سادگی catch {...}.
- کلاس Exception دارای خاصیتی به نام Message است که پیامی با جزئیات خطای رخداده را در بر دارد. بنابراین، اگر بخواهید این پیام را نمایش دهید، مینویسید:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
...
}//catch
- کلاس Exception دارای متدی به نام ToString است که رشتهای را بازمیگرداند که نوع استثنا و همچنین مقدار ویژگی Message را نشان میدهد. بنابراین میتوانیم بنویسیم:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
...
}//catch
به طور جایگزین، میتوانیم بنویسیم:
مترجم مقدار ex.ToString() را به پارامتر {0} اختصاص میدهد.
مثال زیر استثناءای را نشان میدهد که با استفاده از یک عنصر آرایهٔ وجودندار تولید شده است:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// اعلام و مقداردهی اولیه یک آرایه
int[] tab = { 0, 1, 2, 3 };
int i;
//نمایش یک آرایه با استفاده از حلقه for
for (i = 0; i < tab.Length; i++)
Console.WriteLine("tab[{0}]={1}", i, tab[i]);
// نمایش یک آرایه با استفاده از حلقه for-each
foreach (int élmt in tab) {
Console.WriteLine(élmt);
}
//پرتاب یک استثنا
try {
tab[100] = 6;
} catch (Exception e) {
Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
return;
}//try-catch
finally {
Console.WriteLine("finally ...");
}
}
}
}
در مثال بالا، خط ۱۸ یک استثنا ایجاد میکند زیرا آرایه tab عنصری با شاخص ۱۰۰ ندارد. اجرای برنامه نتایج زیر را تولید میکند:
- خط ۹: استثنای [System.IndexOutOfRangeException] رخ داد
- خط ۱۱: عبارت finally (خطوط ۲۳–۲۵) کد اجرا شد، در حالی که خط ۲۱ شامل یک دستور return برای خروج از متد بود. توجه داشته باشید که بند finally همیشه اجرا میشود.
در اینجا یک مثال دیگر آورده شده است که در آن استثناء ناشی از تخصیص یک رشته به یک متغیر عددی زمانی که رشته نمایانگر یک عدد صحیح نیست، مدیریت میشود:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
//مثال ۲
// نام درخواستشده
Console.Write("Nom : ");
// پاسخ را بخوانید
string nom = Console.ReadLine();
// سن درخواست شده است
int age = 0;
bool ageOK = false;
while (!ageOK) {
// سؤال
Console.Write("âge : ");
// پاسخ را بخوانید و تأیید کنید
try {
age = int.Parse(Console.ReadLine());
ageOK = age>=1;
} catch {
}//try-catch
if (!ageOK) {
Console.WriteLine("Age incorrect, recommencez...");
}
}//در حالی که
// نمایش نهایی
Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
}
}
}
- خطوط ۱۵–۲۷: حلقه برای وارد کردن سن شخص
- خط ۲۰: متنی که از طریق صفحهکلید وارد شده است توسط متد int.Parse به یک عدد صحیح تبدیل میشود. این متد در صورتی که تبدیل امکانپذیر نباشد، یک استثنا (exception) پرتاب میکند. به همین دلیل است که این عملیات در داخل یک بلوک try/catch قرار داده شده است.
- خطوط ۲۲–۲۳: اگر یک استثنا پرتاب شود، کنترل به بلوک catch منتقل میشود که در آن هیچ کاری انجام نمیشود. در نتیجه، متغیر بولین ageOK که در خط ۱۴ روی false تنظیم شده بود، روی false باقی میماند.
- خط ۲۱: اگر به این خط برسیم، یعنی تبدیل رشته به عدد صحیح موفقیتآمیز بوده است. با این حال، بررسی میکنیم که عدد صحیح حاصل واقعاً بزرگتر یا مساوی ۱ باشد.
- خطوط ۲۴–۲۶: اگر سن نادرست باشد، یک پیام خطا نمایش داده میشود.
برخی نتایج اجرا:
3.6. مثال کاربردی – V1
هدف ما نوشتن برنامهای برای محاسبه بدهی مالیاتی یک مؤدی است. ما مورد سادهشدهای را در نظر میگیریم که مؤدی تنها حقوق خود را برای اظهار دارد (اعداد ۲۰۰۴ برای درآمد ۲۰۰۳):
- ما تعداد طبقات مالیاتی کارمند را به صورت nbParts = nbEnfants/2 + 1 در صورتی که متأهل نباشد، محاسبه میکنیم، nbEnfants/2 + 2 اگر متأهل باشند، که در آن nbEnfants تعداد فرزندانشان است.
- اگر او حداقل سه فرزند داشته باشد، مستحق دریافت نیمسهم اضافی است.
- درآمد مشمول مالیات او به صورت R = 0.72 × S محاسبه میشود، که در آن S حقوق سالانه او است
- ضریب خانوادگی او به صورت QF = R / nbParts محاسبه میشود
- مالیات I او محاسبه میشود. جدول زیر را در نظر بگیرید:
۴۲۶۲ | 0 | 0 |
8382 | 0.0683 | 291.09 |
۱۴٬۷۵۳ | 0.1914 | ۱٬۳۲۲٫۹۲ |
۲۳٬۸۸۸ | 0.2826 | ۲٬۶۶۸٫۳۹ |
۳۸٬۸۶۸ | 0.3738 | ۴٬۸۴۶٫۹۸ |
۴۷٬۹۳۲ | 0.4262 | ۶٬۸۸۳٫۶۶ |
0 | 0.4809 | ۹۵۰۵٫۵۴ |
هر سطر دارای ۳ فیلد است. برای محاسبه مالیات I، به دنبال اولین سطری میگردیم که QF ≤ field1 باشد. برای مثال، اگر QF = 5000 باشد، سطر را پیدا خواهیم کرد
در این صورت مالیات I برابر با 0.0683*R - 291.09*nbParts خواهد بود. اگر QF به گونهای باشد که شرط QF <= field1 هرگز برقرار نشود، آنگاه ضرایب ردیف آخر استفاده میشوند. در این صورت:
0 0.4809 ۹۵۰۵٫۵۴
که مالیات I را به صورت زیر محاسبه میکند: 0.4809*R - 9505.54*nbParts.
برنامه C# متناظر به شرح زیر است:
using System;
namespace Chap1 {
class Impots {
static void Main(string[] args) {
//جداول دادهای مورد نیاز برای محاسبه مالیات
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
//بازیابی وضعیت تأهل
bool OK = false;
string reponse = null;
while (!OK) {
Console.Write("Etes-vous marié(e) (O/N) ? ");
reponse = Console.ReadLine().Trim().ToLower();
if (reponse != "o" && reponse != "n")
Console.Error.WriteLine("Réponse incorrecte. Recommencez");
else OK = true;
}//در حالی که
bool marie = reponse == "o";
//تعداد فرزندان
OK = false;
int nbEnfants = 0;
while (!OK) {
Console.Write("Nombre d'enfants : ");
try {
nbEnfants = int.Parse(Console.ReadLine());
OK = nbEnfants >= 0;
} catch {
}// سعی کنید
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}//در حالی که
// حقوق
OK = false;
int salaire = 0;
while (!OK) {
Console.Write("Salaire annuel : ");
try {
salaire = int.Parse(Console.ReadLine());
OK = salaire >= 0;
} catch {
}// سعی
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// در حالی که
//محاسبه تعداد سهام
decimal nbParts;
if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// درآمد مشمول مالیات
decimal revenu = 0.72M * salaire;
// ضریب خانوادگی
decimal QF = revenu / nbParts;
//جستجوی بازهٔ مالیاتی متناظر با QF
int i;
int nbTranches = limites.Length;
limites[nbTranches - 1] = QF;
i = 0;
while (QF > limites[i]) i++;
// مالیات
int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
// نمایش نتیجه
Console.WriteLine("Impôt à payer : {0} euros", impots);
}
}
}
- خطوط ۷–۹: مقادیر عددی با پسوند M (Money) مشخص شدهاند تا از نوع decimal باشند.
- خط 16:
- Console.ReadLine() رشته C1 را که از صفحهکلید وارد شده است، بازمیگرداند
- C1.Trim() فاصلههای ابتدایی و انتهایی را از C1 حذف میکند – رشته C2 را بازمیگرداند
- C2.ToLower() رشته C3 را بازمیگرداند، که رشته C2 است که به حروف کوچک تبدیل شده است.
- خط ۲۱: مقدار true یا false از رابطه reponse=="o" به بولین marie تخصیص داده میشود.
- خط ۲۹: رشتهای که از طریق صفحهکلید وارد شده است به نوع int تبدیل میشود. اگر تبدیل ناموفق باشد، یک استثنا پرتاب میشود.
- خط ۳۰: مقدار true یا false از رابطه nbEnfants>=0 به بولین OK اختصاص داده میشود.
- خطوط ۵۵–۵۶: نمیتوان بهسادگی nbEnfants/2 را نوشت. اگر nbEnfants برابر با ۳ بود، ما ۳/۲ را خواهیم داشت، یک تقسیم صحیح که نتیجه آن ۱ خواهد بود نه ۱.۵. بنابراین، ما (اعشاری)nbEnfants را مینویسیم تا یکی از متعاملات تقسیم یک عدد حقیقی باشد و در نتیجه تقسیم بین اعداد حقیقی انجام شود.
در اینجا چند مثال از اجرا آورده شده است:
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros
3.7. آرگومانهای اصلی برنامه
تابع اصلی Main میتواند یک آرایه از رشتهها را بهعنوان پارامتر بپذیرد: String[] (یا string[]). این آرایه شامل آرگومانهای خط فرمان است که برای راهاندازی برنامه استفاده میشوند. بنابراین، اگر برنامه P با دستور زیر (DOS) اجرا شود:
P arg0 arg1 … argn
و اگر تابع Main به صورت زیر تعریف شود:
ما args[0]="arg0"، args[1]="arg1" … را خواهیم داشت. در اینجا یک مثال:
using System;
namespace Chap1 {
class P10 {
static void Main(string[] args) {
// فهرست پارامترهای دریافتی
Console.WriteLine("Il y a " + args.Length + " arguments");
for (int i = 0; i < args.Length; i++) {
Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
}
}
}
}
برای ارسال آرگومانها به کد اجراشده، به شرح زیر عمل کنید:
![]() |
- در [1]: روی پروژه کلیک راست کرده / Properties
- در [2]: برگه [Debug]
- در [3]: آرگومانها را وارد کنید
اجرای کد نتایج زیر را تولید میکند:
توجه داشته باشید که امضا
امضا معتبر است اگر تابع Main هیچ پارامتری را انتظار نداشته باشد.
3.8. شمارشگرها
انومریزاسیون یک نوع داده است که دامنه مقادیر آن مجموعهای از ثابتهای عددی است. برنامهای را در نظر بگیرید که باید ثبتنامهای امتحان را مدیریت کند. در این صورت پنج مورد خواهیم داشت: Passable، AssezBien، Bien، TrèsBien، Excellent.
سپس میتوانیم یک enumeration برای این پنج ثابت تعریف کنیم:
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
درونسازمانی، این پنج ثابت با اعداد صحیح پشت سر هم نمایش داده میشوند، که برای ثابت اول از 0، برای بعدی 1 و به همین ترتیب شروع میشود. یک متغیر میتواند اعلام شود تا این مقادیر را از این شمارهگذاری بپذیرد:
// یک متغیر که مقادیر خود را از شمارشگر 'Mentions' میگیرد
Mentions maMention = Mentions.Passable;
یک متغیر را میتوان با مقادیر مختلف ممکن این شمارهگذاری مقایسه کرد:
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
میتوانید تمام مقادیر موجود در فهرست را بازیابی کنید:
// فهرست برچسبها به صورت رشتهها
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
به همان شیوهای که نوع ساده int معادل ساختار System.Int32 است، نوع ساده enum معادل ساختار System.Enum است. این ساختار یک متد استاتیک به نام GetValues دارد که تمام مقادیر یک نوع شمارشی را که به عنوان پارامتر ارسال شده است، بازیابی میکند. این پارامتر باید یک شی از نوع Type باشد، که کلاسی است که اطلاعاتی در مورد یک نوع داده ارائه میدهد. نوع یک متغیر v از طریق v.GetType() به دست میآید. نوع یک نوع T از طریق typeof(T) به دست میآید. بنابراین در اینجا، maMention.GetType() شیء Type را از شمارش Mentions برمیگرداند، و Enum.GetValues(maMention.GetType()) لیست مقادیر enumeration Mentions را بازمیگرداند.
اگر اکنون بنویسیم
//اشارهها را به صورت اعداد صحیح فهرست میکند
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
خط ۲: متغیر حلقه از نوع عدد صحیح است. سپس لیست مقادیر انومریزیشن را به صورت اعداد صحیح به دست میآوریم. شیء از نوع System.Type که مربوط به نوع داده Mentions است، از طریق typeof(Mentions) به دست میآید. ما میتوانستیم آن را مانند قبل بنویسیم: maMention.GetType().
برنامه زیر نکته گفته شده را نشان میدهد:
using System;
namespace Chap1 {
class P11 {
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
static void Main(string[] args) {
// یک متغیر که مقادیرش از شمارشگر 'Mentions' گرفته میشود
Mentions maMention = Mentions.Passable;
//نمایش مقدار متغیر
Console.WriteLine("mention=" + maMention);
// آزمون با استفاده از مقداری از شمارشگر 'Mentions'
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
// فهرست ورودیها به صورت رشتهها
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
//فهرست موارد به صورت اعداد صحیح
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
}
}
}
نتایج اجرا به شرح زیر است:
3.9. ارسال پارامترها به یک تابع
در اینجا میبینیم که پارامترها چگونه به یک تابع ارسال میشوند. تابع استاتیک زیر را در نظر بگیرید:
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
در تعریف تابع، در خط ۱، a بهعنوان پارامتر رسمی اشاره شده است. این تنها به منظور تعریف تابع changeInt گنجانده شده است. میتوانست به همان آسانی b نامیده شود. اکنون بیایید مثالی از نحوه استفاده از این تابع را بررسی کنیم:
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
در اینجا، در عبارت روی خط ۳، ChangeInt(age)، age پارامتر حقیقی است که مقدار خود را به پارامتر صوری a ارسال میکند. ما به این موضوع علاقهمندیم که یک پارامتر اسمی چگونه مقدار یک پارامتر حقیقی را بازیابی میکند.
3.9.1. ارسال به از روی مقدار
مثال زیر نشان میدهد که پارامترهای تابع بهطور پیشفرض به صورت ارجاعی-توسط-مقدار (pass-by-value) ارسال میشوند؛ یعنی مقدار پارامتر حقیقی در پارامتر صوری متناظر کپی میشود. این دو موجودیت از هم مستقل هستند. اگر تابع پارامتر صوری را تغییر دهد، پارامتر حقیقی کاملاً بدون تغییر باقی میماند.
using System;
namespace Chap1 {
class P12 {
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
}
}
نتایج بهدستآمده به شرح زیر است:
مقدار ۲۰ پارامتر حقیقی age به پارامتر شکلی a (خط ۱۰) کپی شد. سپس این مقدار تغییر داده شد (خط ۱۱). با این حال، پارامتر حقیقی بدون تغییر باقی ماند. این روش ارسال برای پارامترهای ورودی یک تابع مناسب است.
3.9.2. انتقال توسط مرجع
در ارسال به از روی مرجع، پارامتر حقیقی و پارامتر اسمی یک و همان یک واحد هستند. اگر تابع پارامتر اسمی را تغییر دهد، پارامتر حقیقی نیز تغییر میکند. در C#، هر دو باید با کلمه کلیدی ref پیش از خود شروع شوند:
در اینجا یک مثال آورده شده است:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// مثال ۲
int age2 = 20;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
و نتایج اجرا:
پارامتر واقعی برای انعکاس تغییر در پارامتر شکلی بهروزرسانی شده است. این روش ارسال برای پارامترهای خروجی یک تابع مناسب است.
3.9.3. گذراندن به ارجاع با استفاده از کلمه کلیدی «out»
بیایید مثال قبلی را در نظر بگیریم که در آن متغیر age2 قبل از فراخوانی تابع changeInt مقداردهی اولیه نمیشد:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// مثال ۲
int age2;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
هنگامی که این برنامه کامپایل میشود، خطایی رخ میدهد:
این مشکل را میتوان با تخصیص یک مقدار اولیه به age2 حل کرد. به طور جایگزین، کلمه کلیدی ref را میتوان با کلمه کلیدی out جایگزین کرد. این مشخص میکند که پارامتر صرفاً یک پارامتر خروجی است و بنابراین به مقدار اولیه نیاز ندارد:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// مثال ۳
int age3;
ChangeInt3(out age3);
Console.WriteLine("Paramètre effectif age3=" + age3);
}
private static void ChangeInt3(out int a3) {
a3 = 30;
Console.WriteLine("Paramètre formel a3=" + a3);
}
}
}
نتایج اجرای برنامه به شرح زیر است:





