مطالب
C# 7.1 - async Main
پس از فعالسازی C# 7.1، اولین ویژگی جدید C# 7.1، متدهای Main برنامه‌های کنسول (و همچنین WPF و WinForms) هستند که اینبار قابلیت تعریف async را نیز دارند.


نحوه‌ی کار با متدهای async، در متد‌های Main نگارش‌های پیش از C# 7.1

برای کار با متدهای Async نیاز است از واژه‌ی کلیدی await استفاده شود و با قید این واژه، ضروری است واژه‌ی کلیدی async نیز به امضای متد دربرگیرنده‌ی عملیات اضافه گردد؛ اما در نگارش‌های پیشین زبان #C، امکان async تعریف کردن متد Main وجود نداشت. در این حالت می‌بایستی به صورت ذیل عمل می‌شد:
static void Main(string[] args)
{
  MainAsync().GetAwaiter().GetResult();
  Console.ReadLine();
}

private static async Task MainAsync()
{
   using (StreamReader reader = File.OpenText("Program.cs"))
   {
       var message = await reader.ReadToEndAsync().ConfigureAwait(false);
       Console.Write(message);
   }
}
در این حالت برای کار با متد ReadToEndAsync ابتدا نیاز است جهت استفاده‌ی از واژه‌ی کلیدی async، این متد را در یک متد دربرگیرنده‌ی مجزا تعریف کرد تا بتوان تعاریف متداول اعمال غیرهمزمان را به آن متد، که حساسیتی بر روی امضای آن وجود ندارد، اضافه کرد؛ مانند متد MainAsync فوق. سپس می‌توان این متد را به همراه GetAwaiter().GetResult در متد Main اصلی فراخوانی کرد.


نحوه‌ی کار با متدهای async در متدهای Main برنامه‌های مبتنی بر C# 7.1

در زبان سی‌شارپ، متدهای Main برنامه‌های کنسول می‌توانند خروجی‌هایی از نوع void و int داشته باشند؛ به همراه آرگومانی از نوع []string و یا بدون آرگومان. اکنون در سی‌شارپ 7.1، دو امضای دیگر نیز به این مجموعه، جهت کار با اعمال Async اضافه شده‌است: async Task و یا <async Task<int
در این حالت مثال قبل را می‌توان به صورت ذیل خلاصه کرد:
static async Task Main(string[] args)
{
   using (StreamReader reader = File.OpenText("Program.cs"))
   {
      var message = await reader.ReadToEndAsync().ConfigureAwait(false);
      Console.Write(message);
   }
   Console.ReadLine();
}
اولین تغییر، افزودن نوع خروجی async Task به متد Main است. سپس دیگر نیازی به تعریف یک متد دربرگیرنده‌ی دیگر جهت اعمال این امضاء وجود ندارد و می‌توان محتوای متد کمکی MainAsync را مستقیما درون متد Main ذکر کرد.


نگاهی به پشت صحنه‌ی کامپایل async Task Main در C# 7.1

در عمل، کامپایلر سی‌شارپ جهت حفظ سازگاری با نگارش‌های قبلی، مجددا همان متد static void Main را تولید می‌کند و عملیاتی را که در مورد نگارش‌های پیشین توضیح داده شد، تکرار خواهد کرد:
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace ConsoleCS71
{
    internal class Program
    {
        private static async Task Main(string[] args)
        {
            StreamReader reader = File.OpenText("message.txt");
            try
            {
                string str = await reader.ReadToEndAsync();
                string message = str;
                str = (string)null;
                Console.Write(message);
                message = (string)null;
            }
            finally
            {
                if (reader != null)
                    reader.Dispose();
            }
            reader = (StreamReader)null;
            Console.ReadLine();
        }
        [SpecialName]
        private static void <Main>(string[] args)
        {
          Program.Main(args).GetAwaiter().GetResult();
        }
    }
}
همانطور که مشاهده می‌کنید، در اینجا نیز متد Main اصلی همان static void قدیمی است و محتوای داخل متد Main را به یک متد کمکی دربرگیرنده منتقل کرده و در نهایت GetAwaiter().GetResult را بر روی این متد فراخوانی کرده‌است.


در یک برنامه‌ی سی‌شارپ می‌توان بیش از یک متد Main داشت

تعریف بیش از یک متد Main در برنامه‌های سی‌شارپ مجاز است:
namespace ConsoleApp5
{
    class Class1
    {
        static void Main(string[] args)
        {
        }
    }

    class Class2
    {
        static void Main(string[] args)
        {
        }
    }
}
در این حالت تنها کافی است به خواص پروژه مراجعه کرده و متد Main آغازگر را انتخاب کرد:


اما ... اگر دقت کنید، متد async Task Main در اینجا ایندکس نشده‌است که به نظر کمبود نگارش فعلی VS 2017 است.
مطالب
فراخوانی داینامیک اسمبلی‌های آفیس در برنامه

در بسیاری از پروژه‌های دات نت، نیاز به استفاده از فایلهای نرم افزار آفیس، از قبیل ورد و اکسل و ... وجود دارد. برای مثال گاهی لازم است اطلاعات  یک گرید، یا هر منبع داده‌ای، در قالب اکسل به کاربر نمایش داده شود. بدین شکل که این فایلها در زمان اجرا ساخته شده و به کاربر نمایش داده شود .حال فرض کنید شما روی سیستم خودتان Office2007 را نصب کرده اید و به اسمبلی‌های این ورژن دسترسی دارید. البته بدون نیاز به نصب آفیس نیز میتوان به این توابع دسترسی داشت و از آنها در برنامه استفاده کرد که همان استفاده از  Primary Interop Assemblies میباشد. مشکلی که ممکن است پیش آید این است که در کامپیوترهای کاربران ممکن است ورژن‌های مختلفی از آفیس نصب باشد مانند 2003 -2007-2010-2013 و اگر با ورژن اسمبلی‌هایی که فراخوانی‌های فایلهای اکسل از طریق آن انجام شده باشد متفاوت باشد، برنامه اجرا نمی‌شود .

در حالت معمول برای نمایش یک فایل آفیس مثل اکسل در برنامه، ابتدا اسمبلی مربوطه را (اکسل در این مثال) که به نام Microsoft.Office.Interop.Excel میباشد به اسمبلی‌های برنامه اضافه کرده (از طریق add reference) و برای نمایش یک فایل اکسل در زمان اجرا از کدهای زیر استفاده مینماییم :

try
            {
               var application = (Microsoft.Office.Interop.Excel.Application)Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));  
               Workbook  wrkBook;
                var  wbk = application.Workbooks;
                 wrkBook = wbk.Add();
                wrkBook.Activate();
                application.Visible = true;
             }
            catch (Exception ex)
            {
                Error(ex.Message);
            }

حال اگر آفیس 2010 به عنوان مثال در سیستم ما نصب باشد، ورژن این اسمبلی 14 می‌باشد و اگر این برنامه را در کامپیوتر کلاینتی که آفیس 2007 بر روی آن نصب باشد انتشار دهیم اجرا نمیشود. برای حل این مشکل بنده با استفاده از روش dynamic این موضوع را حل کردم و بنظر می‌رسد راه‌های دیگری نیز برای حل آن وجود داشته باشد.

در این روش با توجه به ورژن آفیسی که بر روی سیستم کاربر نصب شده اسمبلی مربوطه را از سیستم کاربر لود کرده و فایلهای آفیس را اجرا مینماییم. در ابتدا تشخیص میدهیم چه ورژنی از آفیس بر روی سیستم کاربر نصب است :

 string strVersion = null;
                dynamic objEApp =Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
                if (objEApp.Version == "12.0")
                {
                    strVersion = "2007";
                }
                else if (objEApp.Version == "14.0")
                {
                    strVersion = "2010";
                }

روش دیگر برای انجام اینکار استفاده از اطلاعات رجیستری ویندوز است : 

            string strEVersionSubKey = "\\Excel.Application\\CurVer";       
            string strValue = null; //Value Present In Above Key
            string strVersion = null; //Determines Excel Version 
            RegistryKey rkVersion = null; //Registry Key To Determine Excel Version 
            rkVersion = Registry.ClassesRoot.OpenSubKey(strEVersionSubKey, false); //Open Registry Key 
            if ((rkVersion != null)) //If Key Exists
            {
                strValue = (string)rkVersion.GetValue(string.Empty); //Get Value 
                strValue = strValue.Substring(strValue.LastIndexOf(".") + 1); //Store Value 
                switch (strValue) //Determine Version
                {
                    case "11":
                        strVersion = "2003";
                        break;
 
                    case "12":
                        strVersion = "2007";
                        break;
 
                    case "14":
                        strVersion = "2010";
                        break;
 
                }
حال با استفاده از تابع ()assembly.load اسمبلی مورد نیاز را لود کرده و در برنامه استفاده مینماییم : 
if (strVersion == "2007")
                {
                    string strAssemblyOff2007 =
                        "Microsoft.Office.Interop.Excel, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c";
                    try
                    {
                        Assembly xslExcelAssembly = Assembly.Load(strAssemblyOff2007); //Load Assembly
                        Type type = xslExcelAssembly.GetTypes().Single(t => t.Name == "ApplicationClass");
                        dynamic application = Activator.CreateInstance(type);
                        var workbooks = application.Workbooks;
                        var workbook = workbooks.Add();
                        var worksheet = workbook.Worksheets[1];
                        workbook.Activate();
                        application.Visible = true;
                    }
                    catch (Exception ex)
                    {
                    }
                }
در این حالت بدون اینکه بدانیم بر روی سیستم کاربر چه ورژنی از آفیس نصب است میتوان فایلهای آفیس را در زمان اجرا لود کرده و استفاده کرد . 
مطالب دوره‌ها
خطا ها و مدیریت خطا (Exception Handling)
مدیریت خطا در #F شبیه به الگوی try catch finally در #C است. برای تعریف خطا از کلمه کلیدی exception استفاده می‌کنیم و یک نام رو به اون اختصاص می‌دهیم و می‌تونیم به صورت اختیاری یک نوع داده رو هم برای این خطا با استفاده از کلمه کلیدی of تعیین کنیم.
exception myError of int
با استفاده از دستور raise می‌تونیم یک exception رو پرتاب کنیم.(به دلیل اینکه در دات نت از دستور throw به معنی پرتاب کردن استفاده می‌کنیم این جا نیز از همین لغت استفاده کردم کما اینکه در #F دستور raise جایگزین throw شده است). البته در جاهایی که قصد ما از پرتاب exception فقط متوقف کردن عملیات و نمایش یک خطا است می‌تونیم از دستور failwith به همراه یک پیغام نیز استفاده کنیم.(یک نمونه از آن را در فصل‌های قبلی مشاهده کردید)
ساختار کلی  try catch finally در #F به صورت زیر است.(تنها تفاوت در کلمه with به جای catch است)
try
// try code here
with
//catch statement here
یا به صورت
try
// try code here
finally
//finally statement here
*نکته مهم: در #F شما اجازه استفاده از finally رو به همراه with ندارید.به همین دلیل من این ساختارو به دو صورت بالا نوشتم.

یک مثال از try with:
exception WrongSecond of int//یک exception تعریف می‌کنیم

let primes =
[ 2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59 ]

// یک تابع برای تست اینکه آیا ثانیه الان در لیست prime وجود دارد یا نه
let testSecond() =
try
let currentSecond = System.DateTime.Now.Second in

// شرط برای اینکه مشخص شود که ثانیه در لیست است یا خیر
if List.exists (fun x -> x = currentSecond) primes then

// اگر بود یک خطا تولید می‌شود
failwith "A prime second"

else

// اگر نیود یک استثنا از نوع wrongSecond پرتاب میشود
raise (WrongSecond currentSecond)

with
// catch کردن استثناها

WrongSecond x ->
printf "The current was %i, which is not prime" x

در کد با در هر خط توضیحات لازم داده شده است. نکته قابل ذکر این است که در #C زمانی که قصد داشته باشیم یک استثنا جدید ایجاد کنیم باید کلاسی جدیدی که از کلاس System.Exception ارث برده باشد(یا هر کلاس دیگری که خود از این System.Exception ارث برده است) ایجاد کنیم و کد‌های مورد نظر رو در اون قرار بدیم. ولی در اینجا (در قسمتی که رنگ آن متفاوت است) به راحتی توانستیم یک استثنا جدید بر اساس نیاز بسازیم.

یک مثال از try finally :
// تابعی برای نوشتن فایل
let writeToFile() =
//ابتدا فایل به صورت متنی ساخته می‌شود
let file = System.IO.File.CreateText("test.txt")
try
// متن مورد نظر در فایل نوشته می‌شود
file.WriteLine("Hello F# users")
finally
//فایل مورد نظر بسته می‌شود.این دستور حتی اگر در هنگام نوشتن فایل استثنا هم رخ بدهد اجرا خواهد شد
file.Dispose()
عملکرد finally در #F دقیقا مشابه با عملکرد finally در #C است. یعنی دستورات بلوک finally همواره (چه استثنا رخ بدهد و چه رخ ندهد) اجرا خواهد شد.

*توجه :
برنامه نویسانی که قبلا با OCaml کدنویسی کرده اند هنگام برنامه نویسی #F از raise کردن‌های زیاد و بی مورد استثناها خودداری کنند. به دلیل نوع معماری CLR پرتاب کردن استثنا و مدیریت آن کمی هزینه بر است (بیشتر از زبان Ocaml). البته این مسئله در زبان‌های تحت دات نت نیز مطرح است کما اینکه در #C نیز مدیریت استثناها رو در بالاترین لایه انجام می‌دهیم و از catch کردن بی مورد استثنائات در لایه‌های زیرین خودداری می‌کنیم.

یک مثال از الگوی Matching در try with

let getNumber msg =
    printf msg;
    try
        int32(System.Console.ReadLine())
    with
        | :? System.FormatException -> -1
        | :? System.OverflowException -> System.Int32.MinValue
        | :? System.ArgumentNullException -> 0

مطالب
Roslyn #4
بررسی API کامپایل Roslyn

Compilation API، یک abstraction سطح بالا از فعالیت‌های کامپایل Roslyn است. برای مثال در اینجا می‌توان یک اسمبلی را از Syntax tree موجود، تولید کرد و یا جایگزین‌هایی را برای APIهای قدیمی CodeDOM و Reflection Emit ارائه داد. به علاوه این API امکان دسترسی به گزارشات خطاهای کامپایل را میسر می‌کند؛ به همراه دسترسی به اطلاعات Semantic analysis. در مورد تفاوت Syntax tree و Semantics در قسمت قبل بیشتر بحث شد.
با ارائه‌ی Roslyn، اینبار کامپایلرهای خط فرمان تولید شده مانند csc.exe، صرفا یک پوسته بر فراز Compilation API آن هستند. بنابراین دیگر نیازی به فراخوانی Process.Start بر روی فایل اجرایی csc.exe مانند یک سری کتابخانه‌های قدیمی نیست. در اینجا با کدنویسی، به تمام اجزاء و تنظیمات کامپایلر، دسترسی وجود دارد.


کامپایل پویای کد توسط Roslyn

برای کار با API کامپایل، سورس کد، به صورت یک رشته در اختیار کامپایلر قرار می‌گیرد؛ به همراه تنظیمات ارجاعاتی به اسمبلی‌هایی که نیاز دارد. سپس کار کامپایلر شروع خواهد شد و شامل مواردی است مانند تبدیل متن دریافتی به Syntax tree و همچنین تبدیل مواردی که اصطلاحا به آن‌ها Syntax sugars گفته می‌شود مانند خواص get و set دار به معادل‌های اصلی آن‌ها. در اینجا کار Semantic analysis هم انجام می‌شود و شامل تشخیص حوزه‌ی دید متغیرها، تشخیص overloadها و بررسی نوع‌های بکار رفته‌است. در نهایت کار تولید فایل باینری اسمبلی، از اطلاعات آنالیز شده صورت می‌گیرد. البته خروجی کامپایلر می‌تواند اسمبلی‌های exe یا dll، فایل XML مستندات اسمبلی و یا فایل‌های .netmudule و .winmdobj مخصوص WinRT هم باشد.
در ادامه، اولین مثال کار با Compilation API را مشاهده می‌کنید. پیشنیاز اجرای آن همان مواردی هستند که در قسمت قبل بحث شدند. یک برنامه‌ی کنسول ساده‌ی .NET 4.6 را آغاز کرده و سپس بسته‌ی نیوگت Microsoft.CodeAnalysis را در آن نصب کنید. در ادامه کدهای ذیل را به پروژه‌ی آماده شده اضافه کنید:
static void firstCompilation()
{
    var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }");
 
    var mscorlibReference = MetadataReference.CreateFromFile(typeof (object).Assembly.Location);
 
    var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
 
    var comp = CSharpCompilation.Create("Demo")
                                .AddSyntaxTrees(tree)
                                .AddReferences(mscorlibReference)
                                .WithOptions(compilationOptions);
 
    var res = comp.Emit("Demo.dll");
 
    if (!res.Success)
    {
        foreach (var diagnostic in res.Diagnostics)
        {
            Console.WriteLine(diagnostic.GetMessage());
        }
    }
}
در اینجا نحوه‌ی کامپایل پویای یک قطعه کد متنی سی‌شارپ را به DLL معادل آن مشاهده می‌کنید. مرحله‌ی اول اینکار، تولید Syntax tree از رشته‌ی متنی دریافتی است. سپس متد CSharpCompilation.Create یک وهله از Compilation API مخصوص #C را آغاز می‌کند. این API به صورت Fluent طراحی شده‌است و می‌توان سایر قسمت‌های آن‌را به همراه یک دات پس از ذکر متد، به طول زنجیره‌ی فراخوانی، اضافه کرد. برای نمونه در این مثال، نحوه‌ی افزودن ارجاعی را به اسمبلی mscorlib که System.Object در آن قرار دارد و همچنین ذکر نوع خروجی DLL یا DynamicallyLinkedLibrary را ملاحظه می‌کنید. اگر این تنظیم ذکر نشود، خروجی پیش فرض از نوع .exe خواهد بود و اگر mscorlib را اضافه نکنیم، نوع int سورس کد ورودی، شناسایی نشده و برنامه کامپایل نمی‌شود.
متدهای تعریف شده توسط Compilation API به یک s جمع، ختم می‌شوند؛ به این معنا که در اینجا در صورت نیاز، چندین Syntax tree یا ارجاع را می‌توان تعریف کرد.
پس از وهله سازی Compilation API و تنظیم آن، اکنون با فراخوانی متد Emit، کار تولید فایل اسمبلی نهایی صورت می‌گیرد. در اینجا اگر خطایی وجود داشته باشد، استثنایی را دریافت نخواهید کرد. بلکه باید خاصیت Success نتیجه‌ی آن‌را بررسی کرده و درصورت موفقیت آمیز نبودن عملیات، خطاهای دریافتی را از مجموعه‌ی Diagnostics آن دریافت کرد. کلاس Diagnostic، شامل اطلاعاتی مانند محل سطر و ستون وقوع مشکل و یا پیام متناظر با آن است.


معرفی مقدمات Semantic analysis

Compilation API به اطلاعات Semantics نیز دسترسی دارد. برای مثال آیا Type A قابل تبدیل به Type B هست یا اصلا نیازی به تبدیل ندارد و به صورت مستقیم قابل انتساب هستند؟ برای درک بهتر این مفهوم نیاز است یک مثال را بررسی کنیم:
        static void semanticQuestions()
        {
            var tree = CSharpSyntaxTree.ParseText(@"
using System;
 
class Foo
{
    public static explicit operator DateTime(Foo f)
    {
        throw new NotImplementedException();
    }
 
    void Bar(int x)
    {
    }
}");
 
            var mscorlib = MetadataReference.CreateFromFile(typeof (object).Assembly.Location);
            var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib).WithOptions(options);
            // var res = comp.Emit("Demo.dll");
 
            // boxing
            var conv1 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Int32),
                comp.GetSpecialType(SpecialType.System_Object)
                );
 
            // unboxing
            var conv2 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Object),
                comp.GetSpecialType(SpecialType.System_Int32)
                );
 
            // explicit reference conversion
            var conv3 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Object),
                comp.GetTypeByMetadataName("Foo")
                );
 
            // explicit user-supplied conversion
            var conv4 = comp.ClassifyConversion(
                comp.GetTypeByMetadataName("Foo"),
                comp.GetSpecialType(SpecialType.System_DateTime)
                );
        }
تا سطر CSharpCompilation.Create این مثال، مانند قبل است و تا اینجا به Compilation API دسترسی پیدا کرده‌ایم. پس از آن می‌خواهیم یک Semantic analysis مقدماتی را انجام دهیم. برای این منظور می‌توان از متد ClassifyConversion استفاده کرد. این متد یک نوع مبداء و یک نوع مقصد را دریافت می‌کند و بر اساس اطلاعاتی که از Compilation API بدست می‌آورد، می‌تواند مشخص کند که برای مثال آیا نوع کلاس Foo قابل تبدیل به DateTime هست یا خیر و اگر هست چه نوع تبدیلی را نیاز دارد؟


برای مثال نتیجه‌ی بررسی آخرین تبدیل انجام شده در تصویر فوق مشخص است. با توجه به تعریف public static explicit operator DateTime در سورس کد مورد آنالیز، این تبدیل explicit بوده و همچنین user defined. به علاوه متدی هم که این تبدیل را انجام می‌دهد، مشخص کرده‌است.
مطالب
پیاده سازی لایه دسترسی به داده ها توسط EF CodeFirst و Service Layer
چندی پیش یک مجموعه آموزشی کامل تحت عنوان EF CodeFirst توسط آقای نصیری در این سایت قرار داده شد که بسیار کامل و زیبا بود.یک پیاده سازی بر اساس این آموزش‌ها تهیه کردم که می‌توانید از اینجا دریافت نمایید و شامل پروژه‌های زیر می‌باشد:  EFCodeFirst-GenericServices.rar

DomainClasses : شامل کلاس‌های مربوطه جهت نگاشت به جداول پایگاه داده ؛ به علاوه کانفیگ‌های مربوطه می‌باشد.
DataLayer : لایه دسترسی به داده‌ها می‌باشد که شامل اینترفیس IUnitOfWork و یک پیاده سازی از آن در شئی Context می‌باشد.
Service Layer : شامل اینترفیس‌ها و کلاس‌های لایه سرویس می‌باشد.ابتدا اینترفیس‌های مربوطه نوشته شده و سپس پیاده سازی مربوط EF آن در یک پوشه دیگر انجام شده است.لازم به ذکر است که دستورات مربوط به کار با EF به علاوه منطق تجاری برنامه در این لایه قرار می‌گیرند.
CommonLib : یک پروژه جهت نگهداری متدهای عمومی و Helper می‌باشد که اینجا مطلب خاصی ندارد و فقط شامل دو پیاده سازی مربوط به تاریخ شمسی می‌باشد که مهم نیستند! از این پروژه در Domain Class و Data Layer جهت تبدیل تاریخ میلادی به شمسی استفاده شده که می‌شد این کار را با کلاس‌های داخلی دات نت نیز انجام داد و این پروژه را حذف نمود.

تنها تفاوت این پیاده سازی با مطالب موجود در سایت، Generic بودن اینترفیس‌ها و کلاس‌های لایه Service می‌باشد که میزان کد نویسی را کاهش داده است.
 
بازخوردهای دوره
بررسی مقدماتی مراحل کامپایل یک قطعه کد سی‌شارپ و آشنایی با OpCodes
یک نمونه از این پروژه‌ها، پروژه Code Refractor است. خلاصه کاری که انجام می‌دهد شامل مراحل زیر است:
- اسمبلی دات نتی را می‌خواند و bytecodes/operations آن‌را استخراج می‌کند.
- پس از آن، نتیجه را تبدیل به یک کد میانی خاص خودش می‌کند.
- این کد میانی خاص خودش را به ++C ترجمه می‌کند.
- نهایتا از یک کامپایلر ++C برای تولید فایل اجرایی نهایی استفاده خواهد کرد.
اطلاعات بیشتر
مطالب
C# 8.0 - Using declarations
یکی دیگر از ویژگی‌های جدید C# 8.0، پشتیبانی از using declarations (اعلان‌های using) در مقابل using statements (عبارات using) پیشین است که سبب می‌شود بتوان کدهای کمتری را برای تعریف آن‌ها نوشت.


مثالی از using declarations

تا پیش از C# 8.0، روش متداول کار با عبارات using به صورت زیر است و به آن استفاده از using statements گفته می‌شود:
    class Program
    {
        static void UsingOld()
        {
            using (var file = new FileStream("input.txt", FileMode.Open))
            using (var reader = new StreamReader(file))
            {
                var s = reader.ReadToEnd();

                // Do something with data
            }
        }
که در نهایت پس از پایان این قطعه کد، هر دو شیء file و reader به صورت خودکار Dispose می‌شوند.
اکنون در C# 8.0 می‌توان قطعه کد فوق را به کمک using declarations به صورت زیر خلاصه کرد:
    class Program
    {
        static void UsingNew(string[] args)
        {
            using Stream file = new FileStream("input.txt", FileMode.Open);
            using StreamReader reader = new StreamReader(file);

            var s = reader.ReadToEnd();

            // Do something with data
        }
که در اینجا پرانتزها و همچنین {} ها، حذف شده‌اند.


میدان دید using declarations

پس از این تغییرات، سؤال مهمی که مطرح می‌شود این است: متغیرهایی که توسط using declaration تعریف می‌شوند، تا چه زمانی زنده نگه داشته می‌شوند. به عبارتی متد UsingOldScope آیا همانند متد UsingNewScope عمل می‌کند؟ آیا متغیر buffer آن همانند متد UsingOldScope خارج از میدان دید usingها قرار می‌گیرد؟
    class Program
    {
        static void UsingNewScope()
        {
            string buffer = null;
            using Stream file = new FileStream("input.txt", FileMode.Open);
            using StreamReader reader = new StreamReader(file);

            buffer = reader.ReadToEnd();

            // Do something with data

            buffer = null;
        }

        static void UsingOldScope(string[] args)
        {
            string buffer = null;

            using (var file = new FileStream("input.txt", FileMode.Open))
            using (var reader = new StreamReader(file))
            {
                buffer = reader.ReadToEnd();
            }

            // Do something with data

            buffer = null;
        }
زمانیکه از using statements استفاده می‌شود (مانند متد UsingOldScope)، توسط آن یک scope نیز تعریف می‌شود (داخل {} ها) که در پایان آن، کار فراخوانی متد Dispose اشیاء IDisposable ارجاعی، به صورت خودکار انجام می‌شود. این فراخوانی نیز توسط کامپایلر در داخل یک قطعه کد try/finally صورت می‌گیرد تا حتی اگر در این بین استثنائی نیز رخ داد، حتما متد Dispose فراخوانی گردد.
اما زمانیکه از using declarations استفاده می‌شود (مانند متد UsingNewScope)، دیگر این {} را نداریم. اینبار scope تعریف شده، تا «پایان متد» ادامه پیدا می‌کند و سپس متد Dispose اشیاء ارجاعی، فراخوانی می‌گردد. بدیهی است در اینجا نیز همانند قبل، همان قطعه کد try/finally توسط کامپایلر جهت فراخوانی متد Dispose، تشکیل خواهد شد. بنابراین اگر بخواهیم متد UsingNewScope را توسط using statements پیشین بازنویسی کنیم، به یک چنین قطعه کدی خواهیم رسید که scope پس از using declarations، تا آخر متد ادامه پیدا می‌کند:
    string buffer = null; 
    using (var file = new FileStream("input.txt", FileMode.Open)) 
    { 
        using (var reader = new StreamReader(file)) 
        { 
            buffer = reader.ReadToEnd(); 
            buffer = null; 
        } 
    }


سؤال: آیا امکان محدود کردن میدان دید using declarations وجود دارد؟

پاسخ: بله. می‌توان با تعریف یک {}، میدان دید متغیرهای ارجاعی توسط using declarations را محدود کرد:
private static void UsingDeclarationWithScope()
{
    {
        using var r1 = new AResource();
        r1.UseIt();
    }  // r1 is disposed here!
    Console.WriteLine("r1 is already disposed");
}
در اینجا جائیکه {} بسته می‌شود، متغیر r1 از میدان دید خارج شده و بلافاصله Dispose خواهد شد.


سؤال: آیا using declarations تمام قابلیت‌های using statements را ارائه می‌دهند؟

پاسخ: خیر. فرض کنید کلاس AResource از نوع IDisposable تعریف شده‌است:
    public class AResource : IDisposable
    {
        public void UseIt() => Console.WriteLine(nameof(UseIt));
        public void Dispose() => Console.WriteLine($"Dispose {nameof(AResource)}");
    }
و سپس متدی، وهله‌ای از این کلاس را باز می‌گرداند:
    class Program
    {
        public static AResource GetTheResource() => new AResource();
با استفاده از using statements، نوشتن چنین قطعه کدی بدون تعریف متغیری مجاز است:
using (GetTheResource())
{
   // do something here
}  // resource is disposed here
اما اگر اینکار را توسط using declarations انجام دهیم، به چندین خطای کامپایلر خواهیم رسید:
using GetTheResource(); // Compiler error
علت اینجا است که برخلاف using statements، ذکر متغیرهای scope برای using declarations اجباری است. برای رفع آن می‌توان از یک discard استفاده کرد:
using var _ = GetTheResource(); // Works fine