مطالب
تبدیل شدن زبان C# 9.0 به یک زبان اسکریپتی با معرفی ویژگی Top Level Programs
اگر به قالب ابتدایی یک برنامه‌ی کنسول #C دقت کنیم، همواره به ساختار استاندارد زیر می‌رسیم:
using System;

namespace CS9Features
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
در اینجا یک سری import، به همراه تعریف فضای نام، تعریف کلاس و تعریف متد Main وجود دارند ... تا بتوان یک سطر Hello World را در کنسول نمایش داد. در این حالت اگر تازه شروع به یادگیری زبان #C کرده باشید، مفاهیم زیادی را باید در جهت درک آن فرا بگیرید؛ برای مثال static چیست؟ args چیست؟ کاربرد فضای نام چیست و غیره. کاری که در C# 9.0 انجام شده، امکان حذف تمام این عوامل در جهت نمایش تک سطر Hello World است که به آن top level programs و یا top level statements گفته می‌شود.


تبدیل قالب پیش‌فرض برنامه‌های کنسول به یک Top level program

در C# 9.0 می‌توان تمام سطرهای فوق را به دو سطر زیر تقلیل داد و خلاصه کرد:
using System;

Console.WriteLine("Hello World!");
این قطعه کد بدون هیچگونه مشکلی در C# 9.0 کامپایل می‌شود و به این ترتیب زبان #C را تبدیل و یا شبیه به یک «زبان اسکریپتی» ساده می‌کند.


روش استفاده از متدهای async در Top level programs

زمانیکه نقطه‌ی آغازین برنامه را تبدیل به یک top level program کردیم، دیگر دسترسی مستقیمی را به متد Main نداریم تا آن‌را async Task دار معرفی کنیم و پس از آن بتوانیم به سادگی با متدهای async کار کنیم. برای رفع این مشکل، کامپایلر فقط کافی است یک await را در قطعه کد شما پیدا کند. خودش به صورت خودکار متد Main غیرهمزمانی را جهت اجرای کدها، تشکیل می‌دهد. به همین جهت برای کار با کدهای async در اینجا، نیاز به تنظیم خاصی نیست و قطعه کد زیر که در آن متد MyMethodAsync را اجرا می‌کند، بدون مشکل کامپایل و اجرا خواهد شد:
using System;
using System.Threading.Tasks;

await MyMethodAsync();
Console.WriteLine("Hello World!");

static async Task MyMethodAsync()
{
   await Task.Yield();
}


روش دسترسی به args در Top level programs

همانطور که در قطعه کد ابتدایی این مطلب مشخص است، متد Main به همراه پارامتر string[] args نیز هست. اما اکنون در Top level programs که فاقد متد Main هستند، چگونه می‌توان به این آرگومان‌های ارسالی توسط کاربر دسترسی یافت؟
پاسخ: پارامتر args نیز هنوز در اینجا قابل دسترسی است؛ فقط به ظاهر مخفی است:
using System;

Console.WriteLine(args[0]);


ارائه‌ی return codes به فراخون در Top level programs

بعضی از برنامه‌های کنسول در انتهای متد Main خود برای مثال return 0 و یا return 1 را دارند؛ که اولی به معنای موفقیت عملیات و دومی به معنای شکست عملیات است. در top level programs نیز می‌توان این return‌ها را در انتهای کار قید کرد:
using System;
Console.WriteLine($"Hello world!");
return 1;
که یک چنین خروجی نهایی را توسط کامپایلر تولید می‌کند:
// <Program>$
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
   private static int <Main>$(string[] args)
   {
     Console.WriteLine("Hello world!");
     return 1;
   }
}


امکان تعریف کلاس‌ها و متدها در Top level programs

در تک فایل program.cs برنامه، در حین کار با Top level programs محدودیتی از لحاظ تعریف متدها، کلاس‌ها و غیره نیست؛ یک مثال:
using System;

var greeter = new Greeter();

var helloTeacher = greeter.Greet("teacher");
var helloStudents = SayHello("students");

Console.WriteLine(helloTeacher);
Console.WriteLine(helloStudents);

static string SayHello(string name)
{
    return "Hello, " + name;
}

public class Greeter
{
    public string Greet(string name)
    {
        return "Hello, " + name;
    }
}
همانطور که مشاهده می‌کنید، در حالت کار اسکریپتی با زبان #C، امکان استفاده‌ی از کلاس‌ها و یا متدها نیز وجود دارد؛ اما با یک شرط: این تعاریف باید پس از Top-level statements قرار گیرند. یعنی اگر متد و کلاس تعریف شده را به بالای فایل انتقال دهید، به خطای کامپایلر زیر خواهید رسید:
Top-level statements must precede namespace and type declarations. [CS9Features]csharp(CS8803)


سطوح دسترسی به کلاس‌ها و متدهای تعریف شده‌ی در Top level programs

اگر قطعه کد مثال قبل را کامپایل کنیم، نمونه‌ی دی‌کامپایل شده‌ی آن به صورت زیر است:
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
  private static void <Main>$(string[] args)
  {
   Greeter greeter = new Greeter();
   string helloTeacher = greeter.Greet("teacher");
   string helloStudents = SayHello("students");
   Console.WriteLine(helloTeacher);
   Console.WriteLine(helloStudents);

   static string SayHello(string name)
   {
    return "Hello, " + name;
   }
  }
}
همانطور که مشاهده می‌کنید، کامپایلر نه فقط نام متدها را تغییر داده‌است، بلکه سطوح دسترسی به آن‌ها را یا private و یا internal تعریف کرده‌است. به این معنا که کلاس‌ها و متدهای تعریف شده‌ی در Top level programs در سایر کتابخانه‌ها و یا برنامه‌ها، قابل استفاده و دسترسی نیستند. البته کلاس public class Greeter به همان صورت public باقی می‌ماند و سطح دسترسی آن تغییری نمی‌کند.


نوع متدهای تعریف شده‌ی در Top level programs

مثال زیر را که یک top level program است، درنظر بگیرید:
using System;

Foo();

var x = 3;

int result = AddToX(4);
Console.WriteLine(result);

static void Foo()
{
    Console.WriteLine("Foo");
}

int AddToX(int y)
{
    return x + y;
}
متد AddToX که static نیست، امکان دسترسی به متغیر x را یافته‌است. با توجه به اینکه متد Main هم static است، چطور چنین چیزی ممکن شده‌است؟
پاسخ: متدهایی که در top level programs تعریف می‌شوند در حقیقت از نوع local functions هستند که در ابتدا در C# 7.0 معرفی شدند و سپس در C# 8.0 امکان تعریف نمونه‌های static آن‌ها نیز میسر شد.
قطعه کد فوق در اصل به صورت زیر کامپایل می‌شود که متدهای AddToX و Foo در آن داخل متد Main تشکیل شده، به صورت local function تعریف شده‌اند:
// <Program>$
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
   private static void <Main>$(string[] args)
   {
     Foo();
     int x = 3;
     int result = AddToX(4);
     Console.WriteLine(result);

     int AddToX(int y)
     {
       return x + y;
     }

     static void Foo()
     {
       Console.WriteLine("Foo");
     }
   }
}
فقط یک local function از نوع static، دسترسی به متغیرهای تعریف شده‌ی در متد Main را ندارد.
مطالب
تزریق وابستگی (dependency injection) به زبان ساده

این مطلب در ادامه‌ی "آشنایی با الگوی IOC یا Inversion of Control (واگذاری مسئولیت)" می‌باشد که هر از چندگاهی یک قسمت جدید و یا کاملتر از آن ارائه خواهد شد.

==============
به صورت خلاصه ترزیق وابستگی و یا dependency injection ، الگویی است جهت تزریق وابستگی‌های خارجی یک کلاس به آن، بجای استفاده مستقیم از آن‌ها در درون کلاس.
برای مثال شخصی را در نظر بگیرید که قصد خرید دارد. این شخص می‌تواند به سادگی با کمک یک خودرو خود را به اولین محل خرید مورد نظر برساند. حال تصور کنید که 7 نفر عضو یک گروه، با هم قصد خرید دارند. خوشبختانه چون تمام خودروها یک اینترفیس مشخصی داشته و کار کردن با آن‌ها تقریبا شبیه به یکدیگر است، حتی اگر از یک ون هم جهت رسیدن به مقصد استفاده شود، امکان استفاده و راندن آن همانند سایر خودروها می‌باشد و این دقیقا همان مطلبی است که هدف غایی الگوی تزریق وابستگی‌ها است. بجای این‌که همیشه محدود به یک خودرو برای استفاده باشیم، بنابر شرایط، خودروی متناسبی را نیز می‌توان مورد استفاده قرار داد.
در دنیای نرم افزار، وابستگی کلاس Driver ، کلاس Car است. اگر موارد ذکر شده را بدون استفاده از تزریق وابستگی‌ها پیاده سازی کنیم به کلاس‌های زیر خواهیم رسید:

//Person.cs
namespace DependencyInjectionForDummies
{
class Person
{
public string Name { get; set; }
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private Car _myCar = new Car();

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
new Driver().DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

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

توضیحات:
کلاس شخص (Person) جهت تعریف مسافرین، اضافه شده؛ سپس کلاس خودرو (Car) که اشخاص را می‌توان به آن اضافه کرده و سپس به مقصد رساند، تعریف گردیده است. همچنین کلاس راننده (Driver) که بر اساس لیست مسافرین، آن‌ها را به خودروی خاص ذکر شده هدایت کرده و سپس آن‌ها را با کمک کلاس خودرو به مقصد می‌رساند؛ نیز تعریف شده است. در پایان هم یک کلاینت ساده جهت استفاده از این کلاس‌ها ذکر شده است.
همانطور که ملاحظه می‌کنید کلاس راننده به کلاس خودرو گره خورده است و این راننده همیشه تنها از یک نوع خودروی مشخص می‌تواند استفاده کند و اگر روزی قرار شد از یک ون کمک گرفته شود، این کلاس باید بازنویسی شود.

خوب! اکنون اگر این کلاس‌ها را بر اساس الگوی تزریق وابستگی‌ها (روش تزریق در سازنده که در قسمت قبل بحث شد) بازنویسی کنیم به کلاس‌های زیر خواهیم رسید:

//ICar.cs
using System;

namespace DependencyInjectionForDummies
{
interface ICar
{
void AddPassenger(Person p);
void Drive();
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car : ICar
{
//همانند قسمت قبل
}
}

//Van.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Van : ICar
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private ICar _myCar;

public Driver(ICar myCar)
{
_myCar = myCar;
}

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
Driver driver = new Driver(new Van());
driver.DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

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

توضیحات:
در اینجا یک اینترفیس جدید به نام ICar اضافه شده است و بر اساس آن می‌توان خودروهای مختلفی را با نحوه‌ی بکارگیری یکسان اما با جزئیات پیاده سازی متفاوت تعریف کرد. برای مثال در ادامه، یک کلاس ون با پیاده سازی این اینترفیس تشکیل شده است. سپس کلاس راننده‌ی ما بر اساس ترزیق این اینترفیس در سازنده‌ی آن بازنویسی شده است. اکنون این کلاس دیگر نمی‌داند که دقیقا چه خودرویی را باید مورد استفاده قرار دهد و از وابستگی مستقیم به نوعی خاص از آن‌ها رها شده است؛ اما می‌داند که تمام خودروها، اینترفیس مشخص و یکسانی دارند. به تمام آن‌ها می‌توان مسافرانی را افزود و سپس به مقصد رساند. در پایان نیز یک راننده جدید بر اساس خودروی ون تعریف شده، سپس یک سری مسافر نیز تعریف گردیده و نهایتا متد DriveToMarket فراخوانی شده است.
به این صورت به یک سری کلاس اصطلاحا loosely coupled رسیده‌ایم. دیگر راننده‌ی ما وابسته‌ی به یک خودروی خاص نیست و هر زمانی که لازم بود می‌توان خودروی مورد استفاده‌ی او را تغییر داد بدون اینکه کلاس راننده را بازنویسی کنیم.
یکی دیگر از مزایای تزریق وابستگی‌ها ساده سازی unit testing کلاس‌های برنامه توسط mocking frameworks است. به این صورت توسط این نوع فریم‌ورک‌ها می‌توان رفتار یک خودرو را تقلید کرد بجای اینکه واقعا با تمام ریز جرئیات آن‌ها بخواهیم سروکار داشته باشیم (وابستگی‌ها را به صورت مستقل می‌توان آزمایش کرد).

مطالب
سری فیبوناچی و دات نت 4 !

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

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

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

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

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

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

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

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

using System;
using System.Linq;

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

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

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

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

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

مطالب
برنامه نویسی موازی بخش دوم (محافظت از مقادیر مشترک)
 در بخش قبلی، مروری کلی بر مفاهیم اصلی برنامه نویسی موازی، از جمله شرایط و نکات استفاده از آن را بررسی کردیم. در انتهای بخش اول عنوان کردیم که در روند برنامه نویسی موازی، اگر دو یا چند Thread به طور مشترک به داده‌ای دسترسی داشته باشند، امکان بروز Race condition وجود خواهد داشت. پس باید کد خود را Thread Safe کنیم. می‌توان برای کنترل رفتارهای عجیب اشیاء در محیط‌های Multi Thread، عنوان Thread Safety را بکار برد.

به طور کلی ۴ روش در #C برای ایجاد Thread Safety وجود دارند:


1- Lock/Monitor
این دو روش یکسان هستند و مانند هم عمل می‌کنند. در واقع در ابتدا روش Monitor وجود داشته و بعد روش lock برای کوتاهی syntax، به صورت بلاکی به #C افزوده شده‌است. این روش تنهای بر روی Thread‌های داخلی App Domain کنترل دارد (اجازه ورود یک Thread) و نمی‌تواند بر روی Thread‌های خارج از این حوزه در محیط‌های Multi Thread محدودیتی اعمال نماید. منظور از Thread‌های داخلی، Thread هایی هستند که داخل Application ما ایجاد شده‌اند.

به تکه کد زیر توجه کنید:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

 class Program
    {
        static int a = 0;
        static int b = 0;
        static Random random = new Random();
        
        static void Main(string[] args)
        {

            Thread obj = new Thread(Division);
            obj.Start();

            Division();
        }

        static void Division()
        {

            for (int i = 0; i <= 500; i++)
            {

                try
                {
                   
                        //Choosing random numbers between 1 to 5
                        a = random.Next(1, 10);
                        b = random.Next(1, 10);


                        //Dividing
                        double ans = a / b;


                        //Reset Variables
                        a = 0;
                        b = 0;

                        Console.WriteLine("Answer : {0} --> {1}", i, ans);
                    
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }

همانطور که در کد بالا ملاحظه می‌کنید، متد Division به صورت Thread Safe پیاده سازی نشده‌است! اما مشکل کجاست!؟

با برسی این متد و عملکرد آن متوجه می‌شویم که این متد در یک چرخه‌ی تکرار ۵۰۰ مرتبه‌ای، دو عدد تصادفی را در بازه‌ی ۱ تا ۱۰، انتخاب کرده و آن‌ها را بر هم تقسیم و متغیر‌های تصادفی را با مقدار ۰ پر می‌کند. همین عمل Reset Variable در این متد، باعث بروز خطا در محیط Multi Thread خواهد شد. بدین صورت که اگر این متد مانند مثال بالا توسط دو Thread مجزا فراخوانی شود، یکبار توسط New Thread و بلافاصله در Thread اصلی Application، احتمال این وجود خواهد داشت که در Thread دوم، بعد از انتخاب دو مقدار تصادفی و درست قبل از عملیات تقسیم، به طور همزمان Thread اول عملیات Reset Variable را انجام دهد که باعث بروز خطای تقسیم بر ۰ در Thread دوم می‌شود. این همان مشکلی است که گاها یافتن آن از طریق Debug بسیار دشوار خواهد بود.
اما با تغییر کد به شکل زیر
class Program
    {
        static int a = 0;
        static int b = 0;
        static Random random = new Random();
        static readonly object _object = new object();
        static void Main(string[] args)
        {

            Thread obj = new Thread(Division);
            obj.Start();

            Division();
        }

        static void Division()
        {

            for (int i = 0; i <= 500; i++)
            {

                try
                {
                    Monitor.Enter(_object);
                   
                        //Choosing random numbers between 1 to 5
                        a = random.Next(1, 10);
                        b = random.Next(1, 10);


                        //Dividing
                        double ans = a / b;


                        //Reset Variables
                        a = 0;
                        b = 0;

                        Console.WriteLine("Answer : {0} --> {1}", i, ans);
                    Monitor.Exit(_object);

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }

مادامی که یک Thread در حالت انتخاب اعداد تصادفی تا تقسیم و اعلام نتیجه می‌باشد، به Thread‌های داخلی دیگر، اجازه‌ی ورود به این بخش که تحت کنترل Monitor می‌باشد داده نخواهد شد. همانطور که گفته شده، بازه‌ی تحت کنترل مانیتور میتواند با بلاک Lock(object) جایگزین شود. شیء object یک شیء مشترک (static) میان تمام اشیاء است برای کنترل ورود Thread‌ها و قفل گزاری مشترک بین این اشیاء.

2- Mutex:
این نوع قفل گزاری به منظور محافظت منابع مشترک برای جلوگیری از ورود Thread‌های بیرونی استفاده می‌شود. منظور از Thread‌های بیرونی Thread‌های یک کامپیوتر است. همچنین می‌توان از Mutex بجای lock نیز استفاده کرد؛ اما به دلیل هدف کاری Mutex، باید هزینه‌ی بیشتری (تقریبا 50 برابر کندتر از Lock) پرداخت کرد.
 static void Main()
  { 
    using (var mutex = new Mutex (false, "dotnettips.info Demo"))
    {
     
      if (!mutex.WaitOne (TimeSpan.FromSeconds (3), false))
      {
        Console.WriteLine ("Another app instance is running. Bye!");
        return;
      }
      RunProgram();
    }
  }
 
  static void RunProgram()
  {
    Console.WriteLine ("Running. Press Enter to exit");
    Console.ReadLine();
  }
در مثال بالا از یک Mutex نام دار استفاده شده است که به ما این امکان را می‌دهد تا به صورت Computer-Wide روی Thread‌ها ایجاد محدودیت نماییم. اگر متد بالا را در دو ترمینال اجرا کنید، نسخه‌ی دوم اجرا نخواهد شد. البته این نکته را در نظر داشته باشید که این امکان در سیتم عامل‌های مبتنی بر Linux غیرفعال است .
Mutex دارای دو متد مهم است :

۱- WaiteOne : شروع Blocking با این متد خواهد بود و اگر بتواند عملیات blocking را انجام دهد مقدار True را باز می‌گرداند. این متد دارای دو ورودی دیگر نیز هست که در مقالات بعدی به طور مفصل به آن‌ها اشاره خواهد شد. اما بطور خلاصه می‌توان اینگونه عنوان نمود که یک پارامتر زمان وجود دارد که مدت زمان انتظار برای Blocking را مشخص می‌کند و پارامتر Boolean دیگری که در حالت synchronization مورد استفاده قرار می‌گیرد و خروج و یا عدم خروج از دامنه synchronization را مشخص می‌کند.

۲- ReleaseMutex : شروع آزاد سازی انحصار، با این متد انجام می‌شود.

هیچگاه نباید یک Mutex را در کد رها کرد؛ زیرا باعث به‌وجود آمدن خطاهایی در کد خواهد شد. روش‌هایی برای رها سازی وجود دارد مانند Dispose کردن Mutex و یا استفاده از متد ReleaseMutex. قبل از خروج از کد باید دقت داشت در بخش هایی از کد که از این نوع قفل گزاری استفاده شده‌است، حتما باید مکانیسم‌های Exception Handling و یا Disposing را برای مدیریت Mutex ایجاد شده اعمال کرد.

3 -Semaphore 
یک نسخه پیشرفته‌تر از Mutex است که می‌تواند برای Thread‌های داخلی و یا خارجی استفاده شود و روی آنها اعمال محدودیت کند. همچنین می‌تواند اجازه‌ی ورود یک تا چند Thread را به بخشی از کد، برای محافظت از منابع بدهد. Semaphore نیز مانند Mutex دارای متد‌های Wait و Release است. یک Semaphore با ظرفیت ورود یک Thread در لحظه همان Mutex است. همچنین از Semaphore‌‌ها می‌توان در متدهای Async نیز استفاده کرد.

4- SemaphoreSlim
در واقع یک نسخه‌ی پیشرفته از Monitor و یک نسخه‌ی سبک وزن از Semaphore است و به همان شکل به شما اجازه‌ی محدودیت گزاری فقط بر روی Thread‌های داخلی را می‌دهد. اما بجای اجازه‌ی ورود فقط یک Thread، به شما این امکان را می‌دهد که اجازه‌ی ورود همزمان یک یا چند Thread را به انتخاب خود بدهید.

هزینه‌ی اعمال محدودیت (قفل گزاری) روی Thread ها
به طور کل هزینه‌ی قفل گزاری بر روی Thread‌ها بالاست. اما در صورت نیاز باید انتخاب درستی از بین موارد عنوان شده را انتخاب نمود. lock/Monitor و SemaphoreSlim دارای کمترین هزینه و Mutex و Semaphore دارای بیشترین هزینه و سربار هستند. اگر در Application‌های بزرگ از Mutex و Semaphore به درستی استفاده نشود، به جد باعث کندی خواهد شد.

در بخش بعدی مقاله، Double-checked locking را مورد بررسی قرار خواهیم داد.
مطالب
اجرای یک Script حاوی دستورات Go در سی شارپ
سلام ؛

سال نو مبارک ! امیدوارم سال بسیار خوبی در پیش داشته باشید :)

از زمانی که استفاده از ORM‌های Code First رایج شده ، اجرای اسکریپت‌های طولانی جهت ایجاد دیتابیس خیلی استفاده ندارد، اما حالت خاص همیشه پیش می‌آید.
مثلا قصد داریم پیش از آغاز برنامه پس از ایجاد دیتابیس توسط Entity Framework به یک سری جداول فیلدی با نوع داده‌ی Geometry اضافه کنیم. یا باید به دیتابیس یک سری Stored Procedure و View اضافه کرد.
Script‌ها ی Generate شده توسط SQL Server حاوی دستور Go هستند. ADO.NET اجرای Script که حاوی Go باشد را پشتیبانی نمی‌کند.
اما روش‌ها و ترفند‌های زیادی برای اجرای یک فایل Script طولانی حاوی دستور Go روی دیتابیس وجود دارد :
        private static string GetScript()
        {
            string path = AppDomain.CurrentDomain.BaseDirectory +
                          @"Scripts\script.sql";
            var file = new FileInfo(path);

            string script = file.OpenText().ReadToEnd();
            return script;
        }

        private static void ExecuteScript()
        {
            string script = GetScript();

            //split the script on "GO" commands
            var splitter = new[] {"\r\nGO\r\n"};
            string[] commandTexts = script.Split(splitter,
                                                 StringSplitOptions.RemoveEmptyEntries);
            foreach (string commandText in commandTexts)
            {
                using (var ctx = new DbContext())
                {
                    if (!string.IsNullOrEmpty(commandText))
                    {
                        ctx.Database.ExecuteSqlCommand(commandText);
                    }
                }
            }
        }

در اینجا به جای آنکه تلاش کنیم یک فایل را روی دیتابیس یک جا اجرا کنیم دستورات را جدا کرده و تک به تک اجرا می‌کنیم. 
بروزرسانی:
مطالب
استفاده از خواص راهبری در Entity framework بجای Join نویسی
یکی از مزایای مهم استفاده از Entity framework، خواص راهبری (navigation properties) آن هستند که امکان تهیه کوئری‌های بین جداول را به سادگی و به نحوی منطقی فراهم می‌کنند.
برای مثال دو جدول شهر‌ها و افراد را درنظر بگیرید. مقصود از تعریف جدول شهر‌ها در اینجا، مشخص سازی محل تولد افراد است:
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }

        [ForeignKey("BornInCityId")]
        public virtual City BornInCity { get; set; }
        public int BornInCityId { get; set; }
    }

    public class City
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Person> People { get; set; }
    }
در ادامه این کلاس‌ها را در معرض دید EF Code first قرار داده:
    public class MyContext : DbContext
    {
        public DbSet<City> Cities { get; set; }
        public DbSet<Person> People { get; set; }
    }


و همچنین تعدادی رکورد آغازین را نیز به جداول مرتبط اضافه می‌کنیم:
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            var city1 = new City { Name = "city-1" };
            var city2 = new City { Name = "city-2" };
            context.Cities.Add(city1);
            context.Cities.Add(city2);

            var person1 = new Person { Name = "user-1", BornInCity = city1 };
            var person2 = new Person { Name = "user-2", BornInCity = city1 };
            context.People.Add(person1);
            context.People.Add(person2);

            base.Seed(context);
        }
    }
در این حالت برای نمایش لیست نام افراد به همراه محل تولد آن‌ها، بنابر روال سابق SQL نویسی، نوشتن کوئری LINQ زیر بسیار متداول است:
    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

            using (var context = new MyContext())
            {
                var peopleAndCitiesList = from person in context.People
                           join city in context.Cities
                           on person.BornInCityId equals city.Id
                           select new
                           {
                              PersonName = person.Name,
                              CityName = city.Name
                           };

                foreach (var item in peopleAndCitiesList)
                {
                    Console.WriteLine("{0}:{1}", item.PersonName, item.CityName);
                }
            }
        }
    }
که حاصل آن اجرای کوئری ذیل بر روی بانک اطلاعاتی خواهد بود:
SELECT 
          [Extent1].[BornInCityId] AS [BornInCityId], 
          [Extent1].[Name] AS [Name], 
          [Extent2].[Name] AS [Name1]
FROM  [dbo].[People] AS [Extent1]
INNER JOIN [dbo].[Cities] AS [Extent2] ON [Extent1].[BornInCityId] = [Extent2].[Id]
این نوع کوئری‌های join دار را به نحو ساده‌تری نیز می‌توان در EF با استفاده از خواص راهبری و بدون join نویسی مستقیم تهیه کرد:
var peopleAndCitiesList = context.People
                                  .Select(person => new
                                                         {
                                                             PersonName = person.Name,
                                                             CityName = person.BornInCity.Name
                                                         });
که دقیقا همان خروجی SQL یاد شده را تولید می‌کند.

مثال دوم:
می‌خواهیم لیست شهرها را بر اساس تعداد کاربر متناظر به صورت نزولی مرتب کنیم:
var citiesList = context.Cities.OrderByDescending(x => x.People.Count());
foreach (var item in citiesList)
{
    Console.WriteLine("{0}", item.Name);
}
همانطور که مشاهده می‌کنید از خواص راهبری در قسمت order by هم می‌شود استفاده کرد. خروجی SQL کوئری فوق به صورت زیر است:
SELECT 
[Project1].[Id] AS [Id], 
[Project1].[Name] AS [Name]
FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Name] AS [Name], 
        (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[People] AS [Extent2]
                WHERE [Extent1].[Id] = [Extent2].[BornInCityId]) AS [C1]
        FROM [dbo].[Cities] AS [Extent1]
)  AS [Project1]
ORDER BY [Project1].[C1] DESC

مثال سوم:
در ادامه قصد داریم لیست شهرها را به همراه تعداد نفرات متناظر با آن‌ها نمایش دهیم:
 var peopleAndCitiesList = context.Cities
                                     .Select(city => new
                                                 {
                                                     InUseCount = city.People.Count(),
                                                     CityName = city.Name
                                                 });

foreach (var item in peopleAndCitiesList)
{
     Console.WriteLine("{0}:{1}", item.CityName, item.InUseCount);
}
در اینجا از خاصیت راهبری People برای شمارش تعداد اعضای متناظر با هر شهر استفاده شده است.
خروجی SQL کوئری فوق به نحو ذیل است:
SELECT 
[Extent1].[Id] AS [Id], 
(SELECT 
        COUNT(1) AS [A1]
        FROM [dbo].[People] AS [Extent2]
        WHERE [Extent1].[Id] = [Extent2].[BornInCityId]) AS [C1], 
[Extent1].[Name] AS [Name]
FROM [dbo].[Cities] AS [Extent1]
مطالب
C# 12.0 - Using aliases for any type
دات‌نت 8 به همراه بهبودهای قابل ملاحظه‌ای در کارآیی برنامه‌های دات‌نتی است و در این بین تعدادی قابلیت جدید را نیز به زبان سی‌شارپ اضافه کرده‌است. در این مطلب ویژگی جدید «Alias any type» آن‌را بررسی می‌کنیم. پیشنیاز کار با این قابلیت تنها نصب SDK دات‌نت 8 است.


امکان تعریف alias، قابلیت جدیدی نیست!

در نگارش‌های پیشین زبان #C نیز می‌توان برای نوع‌های نام‌دار دات‌نت، alias/«نام مستعار» تعریف کرد؛ برای مثال:
using MyConsole = System.Console;

MyConsole.WriteLine("Test console");
Aliasها در قسمت using تعاریف یک کلاس معرفی می‌شوند و یکی از اهدف آن‌ها، کوتاه کردن تعاریف فضاهای نام طولانی است و یا رفع تداخل‌ها؛ همچنین تنها به Named types، محدود هستند و Named types فقط شامل این موارد می‌شوند: classes ،delegates ،interfaces ،records و structs

بنابراین دو حالت تعریف Namespace alias برای کوتاه سازی فضاهای نام طولانی و یا تعریف Type alias برای معرفی یک نام مستعار جدید برای نوعی مشخص، میسر است:
// Namespace alias
using SuperJSON = System.Text.Json;
var document = SuperJSON.JsonSerializer.Serialize("{}");

// Type alias
using SuperJSON = System.Text.Json.JsonSerializer;
var document = SuperJSON.Serialize("{}");

تنها کارکرد نام‌های مستعار، کوتاه و زیبا سازی نام‌های طولانی نیستند. برای مثال گاهی از اوقات ممکن است که بین نام نوع‌های موجود در usingهای جاری، تداخل حاصل شود و برنامه کامپایل نشود. برای مثال فرض کنید که دو using زیر را تعریف کرده‌اید:
using UnityEngine;
using System;

Random rnd = new Random();
کامپایل این برنامه میسر نیست. چون هر دو نوع System.Random و UnityEngine.Random پیشتر تعریف شده‌اند و در اینجا دقیقا مشخص نیست که تامین کننده‌ی شیء Random، کدام فضای نام است. در این حالت می‌توان برای مثال در حین نمونه سازی، فضای نام را صراحتا ذکر کرد:
var rnd = new System.Random();
و یا می‌توان برای آن نام مستعاری نیز تعریف کرد:
using Random = System.Random;
در این حالت دیگر تداخلی وجود نداشته و کامپایلر دقیقا می‌داند که تامین کننده‌ی Random، کدام کتابخانه و کدام فضای نام است.


امکان تعریف alias برای هر نوعی در C# 12.0

محدودیت امکان تعریف alias برای نوع‌های نام‌دار دات‌نت در C# 12.0 برطرف شده و اکنون می‌توان برای انواع و اقسام نوع‌ها مانند آرایه‌ها، tuples و غیره نیز alias تعریف کرد:
using Ints = int[];
using DatabaseInt = int?;
using OptionalFloat = float?;
using Grade= decimal;
using Point3D = (int, int, int);
using Person = (string name, int age, string country);
using unsafe P = char*;
using Matrix = int[][];

Matrix aMatrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
در اینجا امکان تعریف alias را برای آرایه‌ها، nullable value types، نوع‌های توکار، tupleها و حتی نوع‌های unsafe، مشاهده می‌کنید.
یک نکته: امکان تعریف alias برای nullable reverence types وجود ندارد.


بررسی یک مثال C# 12.0

در اینجا محتویات یک فایل Program.cs یک برنامه‌ی کنسول دات‌نت 8 را مشاهده می‌کنید:
using MyConsole = System.Console;
using Person = (string name, int age, string country);

Person person = new("User 1", 33, "Iran");
Console.WriteLine(person);
PrintPerson(person);

MyConsole.WriteLine("Test console");

static void PrintPerson(Person person)
{
   MyConsole.WriteLine($"{person.name}, {person.age}, {person.country}");
}
در این مثال برای یک نوع tuple سفارشی، یک alias به نام Person تعریف شده و سپس از آن برای نمونه سازی یک شیء جدید و یا ارسال آن به عنوان یک پارامتر متد، استفاده شده‌است. خروجی برنامه‌ی فوق به صورت زیر است:
(User 1, 33, Iran)
User 1, 33, Iran
Test console


چه زمانی بهتر است از قابلیت تعریف نام‌های مستعار نوع‌ها و یا فضاهای نام استفاده شود؟

اگر یک نام طولانی را بتوان به این صورت خلاصه کرد، مفید هستند؛ برای مثال ساده سازی تعریف یک لیست طولانی به صورت زیر:
using Companies = System.Collections.Generic.List<Company>;

Companies GetCompanies()
{
   // logic here
}

class Company
{
   public string Name;
   public int Id;
}
و مثالی دیگر در این زمینه، کوتاه سازی تعاریف متداول جنریک طولانی است:
using EventHandlers = System.Collections.Generic.IEnumerable<System.Func<System.Threading.Tasks.Task>>;

 و یا اگر بتوانند رفع تداخلی را حاصل کنند، بکارگیری آن‌ها ضروری است (مانند مثال شیء Random ابتدای بحث) و یا اگر بتوانند از تکرار تعریف یک tuple جلوگیری کنند، ذکر آن‌ها یک refactoring مثبت به‌شمار می‌رود؛ مانند مثال زیر که در آن از تعریف نوع tuple ای، دوبار استفاده شده‌است:
using Country = (string Abbreviation, string Name);

Country GetCountry(string abbreviation)
{
   // Logic here
}

List<Country> GetCountries()
{
   // Logic here
}
 اما ... آیا واقعا تعاریفی مانند ذیل، مفید یا ضروری هستند؟
using Ints = int[];
using DatabaseInt = int?;
using OptionalFloat = float?;
اینجا فقط قطعه کدی اضافی را که بیشتر سبب سردرگمی و بالا بردن درجه‌ی پیچیدگی برنامه می‌شود، تولید کرده‌ایم. خوانایی و سادگی درک برنامه در این حالت کاهش پیدا می‌کند.


میدان دید نام‌های مستعار

به صورت پیش‌فرض، تمام نام‌های مستعار تنها در داخل همان فایلی که تعریف شده‌اند، قابل استفاده می‌باشند. از زمان C# 10.0 ، می‌توان پیش از واژه‌ی کلیدی using از واژه‌ی کلیدی global نیز استفاده کرد تا تعریف آن‌ها فقط در پروژه‌ی جاری به صورت سراسری قابل دسترسی شود.
به همین جهت اگر نوعی قرار است در سایر پروژه‌ها استفاده شود، بهتر است از global using استفاده نشده و از همان روش‌های متداول تعریف records و یا classes استفاده شود.
مطالب
افزودن خودکار کلاس‌های تنظیمات نگاشت‌ها در EF Code first
اگر از روش Fluent-API برای تنظیم و افزودن نگاشت‌های کلاس‌ها استفاده کنیم، با زیاد شدن آن‌ها ممکن است در این بین، افزودن یکی فراموش شود یا کلا اضافه کردن دستی آن‌ها در متد OnModelCreating آنچنان جالب نیست. می‌شود این‌کار را به کمک Reflection ساده‌تر و خودکار کرد:
        void loadEntityConfigurations(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var configurations = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsGenericType &&
                                           type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                                    .ToList();

            configurations.ForEach(type =>
               {
                   dynamic instance = Activator.CreateInstance(type);
                   modelBuilder.Configurations.Add(instance);
               });
        }
در این متد، در یک اسمبلی مشخص و فضای نامی در آن، به دنبال کلاس‌هایی از نوع EntityTypeConfiguration خواهیم گشت. در ادامه این کلاس‌ها وهله سازی شده و به صورت خودکار به modelBuilder اضافه می‌شوند.

یک مثال کامل که بیانگر نحوه استفاده از متد فوق است:
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Reflection;

namespace EFGeneral
{
    public class User
    {
        public int UserNumber { get; set; }
        public string Name { get; set; }
    }

    public class UserConfig : EntityTypeConfiguration<User>
    {
        public UserConfig()
        {
            this.HasKey(x => x.UserNumber);
            this.Property(x => x.Name).HasMaxLength(450).IsRequired();
        }
    }

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // modelBuilder.Configurations.Add(new UserConfig());

            var asm = Assembly.GetExecutingAssembly();
            loadEntityConfigurations(asm, modelBuilder, "EFGeneral");
        }

        void loadEntityConfigurations(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var configurations = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsGenericType &&
                                           type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                                    .ToList();

            configurations.ForEach(type =>
               {
                   dynamic instance = Activator.CreateInstance(type);
                   modelBuilder.Configurations.Add(instance);
               });
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            context.Users.Add(new User { Name = "name-1" });
            context.Users.Add(new User { Name = "name-2" });
            context.Users.Add(new User { Name = "name-3" });
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var user1 = context.Users.Find(1);
                if (user1 != null)
                    Console.WriteLine(user1.Name);
            }
        }
    }
}
در این مثال، در متد OnModelCreating بجای اضافه کردن دستی تک تک تنظیمات تعریف شده، از متد loadEntityConfigurations جهت یافتن آن‌ها در اسمبلی جاری و فضای نام مشخصی به نام EFGeneral استفاده شده است.
مطالب
استخراج متن از فایل‌های PDF توسط iTextSharp
پیشنیاز
نحوه ذخیره شدن متن در فایل‌های PDF

حتما نیاز است پیشنیاز فوق را یکبار مطالعه کنید تا علت خروجی‌های متفاوتی را که در ادامه ملاحظه خواهید نمود، بهتر مشخص شوند. همچنین فایل PDF ایی که مورد بررسی قرار خواهد گرفت، همان فایلی است که توسط متد writePdf ذکر شده در پیشنیاز تهیه شده است.

دو کلاس متفاوت برای استخراج متن از فایل‌های PDF در iTextSharp وجود دارند:
الف) SimpleTextExtractionStrategy

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;

namespace TestReaders
{
    class Program
    {
        private static void readPdf1()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
               var text = PdfTextExtractor.GetTextFromPage(reader, i, new SimpleTextExtractionStrategy());
                File.WriteAllText("page-" + i + "-text.txt", text);
            }
            reader.Close();
        }

        static void Main(string[] args)
        {
            readPdf1();
        }
    }
}
مثال فوق، متن موجود در تمام صفحات یک فایل PDF را در فایل‌های txt جداگانه‌ای ثبت می‌کند. برای نمونه اگر از PDF پیشنیاز یاد شده استفاده کنیم، خروجی آن به نحو زیر خواهد بود:
 Test
ld Wor llo He
Hello People
علت آن نیز پیشتر بررسی گردید. متن، در این فایل ویژه در مختصات خاصی ترسیم شده است. حاصل از دیدگاه خواننده نهایی بسیار خوانا است؛ اما خروجی hello world متنی جالبی از آن استخراج نمی‌شود. SimpleTextExtractionStrategy دقیقا بر اساس همان عملگر‌های Tj و همچنین منابع صفحه، عبارات را یافته و سر هم می‌کند.


ب) LocationTextExtractionStrategy

همان مثال قبل را درنظر بگیرید، اینبار به شکل زیر:
        private static void readPdf2()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy());
                File.WriteAllText("page-" + i + "-text.txt", text);
            }
            reader.Close();
        }
کلاس LocationTextExtractionStrategy هوشمند‌تر عمل کرده و بر اساس عملگرهای هندسی یک فایل PDF، سعی می‌کند جملات و حروف را کنار هم قرار دهد و در نهایت خروجی متنی بهتری را تولید کند. برای نمونه اینبار خروجی متنی حاصل به صورت زیر خواهد بود:
 Test
Hello World
Hello People
این خروجی با آنچه که در صفحه نمایش داده می‌شود تطابق دارد.


استخراج متون فارسی از فایل‌های PDF توسط iTextSharp

روش‌های فوق با PDFهای فارسی هم کار می‌کنند اما خروجی حاصل آن مفهوم نیست و نیاز به پردازش ثانوی دارد. ابتدا مثال زیر را درنظر بگیرید:
        static void writePdf2()
        {
            using (var document = new Document(PageSize.A4))
            {
                var writer = PdfWriter.GetInstance(document, new FileStream("test.pdf", FileMode.Create));
                document.Open();

                FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
                var tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H);

                ColumnText.ShowTextAligned(
                            canvas: writer.DirectContent,
                            alignment: Element.ALIGN_CENTER,
                            phrase: new Phrase("تست می‌شود", tahoma),
                            x: 100,
                            y: 100,
                            rotation: 0,
                            runDirection: PdfWriter.RUN_DIRECTION_RTL,
                            arabicOptions: 0);                
            }

            Process.Start("test.pdf");
        }
از متد فوق، برای تولید یک فایل PDF که متنی فارسی را نمایش می‌دهد استفاده خواهیم کرد. اگر متد readPdf2 را که به همراه LocationTextExtractionStrategy تعریف شده است، بر روی فایل حاصل فراخوانی کنیم، خروجی آن به صورت زیر خواهد بود:
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
برای تبدیل آن به یونیکد خواهیم داشت:
        private static void readPdf2()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy());                
                text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text));
                File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8);
            }
            reader.Close();
        }
اکنون خروجی ثبت شده در فایل متنی حاصل به صورت زیر است:
 ﺩﻮﺷﻲﻣ ﺖﺴﺗ
دقیقا به همان نحوی است که iTextSharp و اکثر تولید کننده‌های PDF فارسی از آن استفاده می‌کنند و اصطلاحا چرخاندن حروف یا تولید Glyph mirrors صورت می‌گیرد. روش‌های زیادی برای چرخاندن حروف وجود دارند. در ادامه از روشی استفاده خواهیم کرد که خود ویندوز در کارهای داخلی‌اش از آن استفاده می‌کند:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;

namespace TestReaders
{
    [SuppressUnmanagedCodeSecurity]
    class GdiMethods
    {
        [DllImport("GDI32.dll")]
        public static extern bool DeleteObject(IntPtr hgdiobj);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern uint GetCharacterPlacement(IntPtr hdc, string lpString, int nCount, int nMaxExtent, [In, Out] ref GcpResults lpResults, uint dwFlags);

        [DllImport("GDI32.dll")]
        public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    }

    [StructLayout(LayoutKind.Sequential)]
    struct GcpResults
    {
        public uint lStructSize;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string lpOutString;
        public IntPtr lpOrder;
        public IntPtr lpDx;
        public IntPtr lpCaretPos;
        public IntPtr lpClass;
        public IntPtr lpGlyphs;
        public uint nGlyphs;
        public int nMaxFit;
    }

    public class UnicodeCharacterPlacement
    {
        const int GcpReorder = 0x0002;
        GCHandle _caretPosHandle;
        GCHandle _classHandle;
        GCHandle _dxHandle;
        GCHandle _glyphsHandle;
        GCHandle _orderHandle;

        public Font Font { set; get; }

        public string Apply(string lines)
        {
            if (string.IsNullOrWhiteSpace(lines))
                return string.Empty;

            return Apply(lines.Split('\n')).Aggregate((s1, s2) => s1 + s2);
        }

        public IEnumerable<string> Apply(IEnumerable<string> lines)
        {
            if (Font == null)
                throw new ArgumentNullException("Font is null.");

            if (!hasUnicodeText(lines))
                return lines;

            var graphics = Graphics.FromHwnd(IntPtr.Zero);
            var hdc = graphics.GetHdc();
            try
            {
                var font = (Font)Font.Clone();
                var hFont = font.ToHfont();
                var fontObject = GdiMethods.SelectObject(hdc, hFont);
                try
                {
                    var results = new List<string>();
                    foreach (var line in lines)
                        results.Add(modifyCharactersPlacement(line, hdc));
                    return results;
                }
                finally
                {
                    GdiMethods.DeleteObject(fontObject);
                    GdiMethods.DeleteObject(hFont);
                    font.Dispose();
                }
            }
            finally
            {
                graphics.ReleaseHdc(hdc);
                graphics.Dispose();
            }
        }

        void freeResources()
        {
            _orderHandle.Free();
            _dxHandle.Free();
            _caretPosHandle.Free();
            _classHandle.Free();
            _glyphsHandle.Free();
        }

        static bool hasUnicodeText(IEnumerable<string> lines)
        {
            return lines.Any(line => line.Any(chr => chr >= '\u00FF'));
        }

        void initializeResources(int textLength)
        {
            _orderHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned);
            _dxHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned);
            _caretPosHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned);
            _classHandle = GCHandle.Alloc(new byte[textLength], GCHandleType.Pinned);
            _glyphsHandle = GCHandle.Alloc(new short[textLength], GCHandleType.Pinned);
        }

        string modifyCharactersPlacement(string text, IntPtr hdc)
        {
            var textLength = text.Length;
            initializeResources(textLength);
            try
            {
                var gcpResult = new GcpResults
                {
                    lStructSize = (uint)Marshal.SizeOf(typeof(GcpResults)),
                    lpOutString = new String('\0', textLength),
                    lpOrder = _orderHandle.AddrOfPinnedObject(),
                    lpDx = _dxHandle.AddrOfPinnedObject(),
                    lpCaretPos = _caretPosHandle.AddrOfPinnedObject(),
                    lpClass = _classHandle.AddrOfPinnedObject(),
                    lpGlyphs = _glyphsHandle.AddrOfPinnedObject(),
                    nGlyphs = (uint)textLength,
                    nMaxFit = 0
                };
                var result = GdiMethods.GetCharacterPlacement(hdc, text, textLength, 0, ref gcpResult, GcpReorder);
                return result != 0 ? gcpResult.lpOutString : text;
            }
            finally
            {
                freeResources();
            }
        }
    }
}
از کلاس فوق در هر برنامه‌ای که راست به چپ را به نحو صحیحی پشتیبانی نمی‌کند، می‌توان استفاده کرد؛ خصوصا برنامه‌های گرافیکی.
در اینجا برای اصلاح متد readPdf2 خواهیم داشت:
        private static void readPdf2()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy());
                text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text));
                text = new UnicodeCharacterPlacement
                {
                    Font = new System.Drawing.Font("Tahoma", 12)
                }.Apply(text);
                File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8);
            }
            reader.Close();
        }
اگر خروجی متد اصلاح شده فوق را بررسی کنیم، دقیقا به «تست می‌شود» خواهیم رسید.

سؤال: آیا این روش با تمام PDFهای فارسی کار می‌کند؟
پاسخ: خیر! همانطور که در پیشنیاز مطلب جاری عنوان شد، در یک حالت خاص، PDF writer می‌تواند شماره Glyphها را کاملا عوض کرده و در فایل PDF نهایی ثبت کند. خروجی حاصل در برنامه Adobe reader خوانا است، چون نمایش را بر اساس اطلاعات هندسی Glyphها انجام می‌دهد؛ اما خروجی متنی آن به نوعی obfuscated است چون مثلا حرف A آن به کاراکتر مرسوم دیگری نگاشت شده است.
مطالب
استفاده از Async&Await برای پیاده سازی متد های Async
در این مطلب می‌خوام روش استفاده از  Async&Await رو براتون بگم. Async&Await خط و مشی جدید Microsoft برای تولید متد‌های Async هستش که نوشتن این متدها رو خیلی جذاب کرده و کاربردهای خیلی زیادی هم داره. مثلا هنگام استفاده از Web Api در برنامه‌های تحت ویندوز نظیر WPF این روش خیلی به ما کمک می‌کنه و در کل نوشتن  Parallel Programming را خیلی جالب کرده.
برای اینکه بتونم قدرت و راحتی کار با این ابزار رو به خوبی نشون بدم ابتدا یک مثال رو به روشی قدیمی‌تر پیاده سازی می‌کنم. بعد پیاده سازی همین مثال رو به روش جدید بهتون نشون می‌دم.
می‌خوام یک برنامه بنویسم که لیستی از محصولات رو به صورت Async  در خروجی چاپ کنه. ابتدا کلاس مدل:
public class Product
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }
حالا کلاس ProductService رو می‌نویسم:
public class ProductService
    {
        public ProductService()
        {
            ListOfProducts = new List<Product>();
        }

        public List<Product> ListOfProducts
        {
            get;
            private set;
        }

        private void InitializeList( int toExclusive )
        {
            Parallel.For( 0 , toExclusive , ( int counter ) =>
            {
                ListOfProducts.Add( new Product()
                {
                    Id = counter ,
                    Name = "DefaultName" + counter.ToString()
                } );
            } );
        }

        public IAsyncResult BeginGetAll( AsyncCallback callback , object state )
        {
            var myTask = Task.Run<IEnumerable<Product>>( () =>
            {
                InitializeList( 100 );
                return ListOfProducts;
            } );
            return myTask.ContinueWith( x => callback( x ) );
        }

        public IEnumerable<Product> EndGetAll( IAsyncResult result )
        {
            return ( ( Task<IEnumerable<Product>> )result ).Result;
        }      
    }
در کلاس بالا دو متد مهم دارم. متد اول آن BeginGetAll است و همونطور که می‌بینید خروجی اون از نوع IAsyncResult است و باید هنگام استفاده، اونو به متد EndGetAll پاس بدم تا خروجی مورد نظر به دست بیاد.
متد InitializeList به تعداد ورودی آیتم به لیست اضافه می‌کند و اونو به CallBack میفرسته. در نهایت برای اینکه بتونم از این کلاس‌ها استفاده کنم باید به صورت زیر عمل بشه:
class Program
    {
        static void Main( string[] args )
        {
            GetAllProducts().ToList().ForEach( ( Product item ) => 
            {
                Console.WriteLine( item.Name );
            } );

            Console.ReadLine();
        }

        public static IEnumerable<Product> GetAllProducts()
        {
            ProductService service = new ProductService();

            var output = Task.Factory.FromAsync<IEnumerable<Product>>( service.BeginGetAll , service.EndGetAll , TaskCreationOptions.None );
            return output.Result;            
        }
        
    }
خیلی راحت بود؛ درسته. خروجی مورد نظر رو می‌بینید:


حالا همین کلاس بالا رو به روش Async&Await می‌نویسم:
 public async Task<IEnumerable<Product>> GetAllAsync()
        {
            var result = Task.Run( () =>
            {
                InitializeList( 100 );
                return ListOfProducts;
            } );
            return await result;
        }
در متد بالا به جای استفاده از 2 متد از یک متد GetAllAsync استفاده کردم که خروجی آون از نوع async Task<IEnumerable<Product>> بود و برای استفاده از اون کافیه در کلاس Program کد زیر رو بنویسم
class Program
    {
        static void Main( string[] args )
        {
            GetAllProducts().Result.ToList().ForEach( ( Product item ) => 
            {
                Console.WriteLine( item.Name );
            } );

            Console.ReadLine();
        }

        public static async Task<IEnumerable<Product>> GetAllProducts()
        {
            ProductService service = new ProductService();

            return await service.GetAllAsync();
        }
        
    }
فکر کنم همتون موافقید که روش Async&Await هم از نظر نوع کد نویسی و هم از نظر راحتی کار خیلی سرتره. یکی از مزایای مهم این روش اینه که همین مراحل رو می‌تونید در هنگام استفاده از WCF در پروژه تکرار کنید. به خوبی کار می‌کنه.