مطالب
آیا دوران پادشاهی اوراکل در حوزه‌ی مدیریت پایگاه‌های داده عملیاتی به پایان رسیده است؟
از سال 1970 تا به حال سیستم‌های مدیریت پایگاه داده عملیاتی – ODBMS - مختلفی ایجاد شده‌اند. بعضی از آنها به مرور زمان از بین رفته‌اند و برخی قدرتمند‌تر شده‌اند. در دهه‌های اخیر بین سیستم‌های مدیریت پایگاه داده عملیاتی، محصولات شرکت‌های اوراکل، مایکروسافت، IBM و SAP از بقیه موفق‌تر بوده‌اند. اما مسلما در این بین بهترین سیستم مدیریت پایگاه داده، محصول شرکت اوراکل بوده است و سخن گزافی نیست که بگوییم محصول شرکت اوراکل در دهه‌های اخیر در بین محصولات دیگر شرکت‌ها پادشاهی می‌کرده است .
تا حدود 4 سال پیش بین کیفیت oracle db و sql server اختلاف فاحشی وجود داشت. چه از نظر سرعت و چه از نظر دیگر امکانات، اوراکل کاملا برتر از رقیب خود بود. در نسخه‌ی sql server 2012، امکانات قابل توجهی به محصول شرکت مایکروسافت افزوده شد. از مهمترین این امکانات می‌توان به ویژگی AlwaysOn و ColumnStore Index‌ها اشاره کرد. امکانات این نسخه باعث شد که اختلاف بین oracle db و sql server تا حدی کاهش یابد. مایکروسافت سرانجام در نسخه‌ی sql server 2014 خود تغییرات اساسی بوجود آورد. مهمترین این تغییرات ایجاد موتور درونی In-Memory OLTP می‌باشد که برای تراکنش‌های درون حافظه بهینه شده است. با استفاده از امکانات این نسخه می‌توان بدون نیاز به دوباره نویسی محصولات، سرعت اجرای کوئری‌های آنها را به طور متوسط ده برابر کرد. در شکل ذیل ساختار جدید sql server مشاهده می‌شود.


شرکت بوین که یک شرکت مشهور ارائه خدمات آنلاین و پیش بینی بازی‌های ورزشی است و در هر لحظه، کاربران آنلاین بسیاری در وب سایت شرکت، کوئری اجرا می‌کنند، از قابلیت‌های جدید اس کیو ال سرور 2014 استفاده کرده است و با استفاده از این قابلیت‌ها توانسته سرعت اجرای پرس و جو‌های مشتریانش را از 15 هزار پرس و جو در ثانیه به 250 هزار پرس و جو در ثانیه برساند. در نتیجه کارایی سرور این شرکت 16 برابر شده است.


در تحقیقی دیگر، یک محقق، با استفاده از قابلیت‌های جدید اس کیو ال سرور 2014 توانسته است دو رکورد جدید را از اجرای کوئری‌های انبار داده ای برای حجم‌های 3 ترابایت و 10 ترابایت و نوع پارتیشن بندی نشده به ثبت برساند و رکورد‌های قبلی را که متعلق به اوراکل بوده، بشکند. این محقق توانسته 404005 کوئری نسبتا سنگین انبار داده‌ای را در پایگاه داده‌ای با 10 ترابایت اطلاعات، در یک ساعت اجرا کند و رکورد قبلی را که متعلق به اوراکل و برابر 377594 کوئری با همین شرایط بوده، بشکند. همچنین هزینه‌ی اجرای کوئری‌های سرور اس کیو ال مذکور برابر 2.04 دلار در هر ساعت اجرای کوئری بوده است. به این معنی است که کمتر از نصف هزینه‌ی مشابه در رکورد ثبت شده‌ی اوراکل که برابر 4.65 دلار در ساعت اجرای کوئری بوده است، هزینه داشته است.


در واقع اگر بخواهیم سیستم‌های مدیریت پایگاه داده عملیاتی را رتبه بندی کنیم، به جز سرعت، باید عوامل مختلفی را در نظر بگیریم که چنین کاری نیاز به همکاری گروهی بزرگ دارد. خوشبختانه چنین گروه‌هایی وجود دارند و آن قدر معتبر هستند که اکثر شرکت‌های بزرگ به آمار‌های آنها استناد می‌کنند. در فناوری‌های مربوط به آی تی، برای رسیدن به معتبر‌ترین نتایج باید به گزارش‌های ارائه شده‌ی شرکت گارتنر رجوع کنیم. گارتنر، شرکت پژوهشی و مشاوره‌ی آمریکایی است، که در زمینه‌ی ارائه خدمات برون‌سپاری، تحقیق و پژوهش و مشاوره فناوری اطلاعات فعالیت می‌نماید. این شرکت در سال 1979 راه‌اندازی شد و در سال 2014 بیش از 6500 نفر کارمند داشته که در 85 کشور بوده‌اند. در این بین حدود 1500 نفر از آنها در بخش تحقیق و توسعه فعالیت داشته‌اند. همچنین در این سال، درآمد شرکت گارتنر که عمدتا از طریق مشاوره دادن به شرکت‌های مختلف بوده، بیش از 2 میلیارد دلار در سال 2014 بوده است.
شرکت گارتنر معمولا خلاصه‌ی نتیجه‌ی بررسی‌های خود را در نمودارهایی خاص به نام مربع جادویی گارتنر ارائه می‌کند. در این نمودار، قابلیت‌های اجرایی که بیانگر کیفیت فعلی محصول هستند، در محور عمودی نمایش داده می‌شوند و از پایین به بالا زیاد می‌شوند. یعنی هر چه محصولی بالاتر باشد، در حال حاضر کیفیت بهتری دارد. محور افقی نمودار بیانگر بصیرت و آینده نگری محصول می‌باشد و از چپ به راست زیاد می‌شود. به این ترتیب رهبران یک حوزه‌ی خاص، در ربع بالا و سمت راست مربع جای می‌گیرند.


حال که با نحوه‌ی تفسیر مربع جادویی گارتنر آشنا شدیم، به بررسی نمودار‌های مربوط به سیستم‌های مدیریت پایگاه داده عملیاتی در سه سال اخیر می‌پردازیم.
در شکل ذیل می‌بینیم که در سال 2013 و پس از ارائه‌ی نسخه‌ی sql server 2012 توسط مایکروسافت، اوراکل همچنان پیشتاز است و شرکت‌های مایکروسافت، آی بی ام و SAP پس از آن قرار گرفته‌اند. البته در این سال شرکت مایکروسافت فاصله‌ی زیاد قبلی خود را با اوراکل، کم کرده است.


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


اما چند روز پیش در تاریخ 12 اکتبر 2015، شرکت گارتنر گزارشی ارائه کرد که خیلی از فعالان آی تی را شگفت زده کرد. این گزارش در حال حاضر در وب سایت شرکت گارتنر قابل دسترسی است؛ ولی معمولا گارتنر پس از مدتی آن را از حالت رایگان به پولی تغییر می‌دهد.
لینک موقت گزارش

در گزارش سال 2015 و پس از ارائه‌ی نسخه‌ی sql server 2014 و کاربردی شدن و تست قابلیت‌های آن در عمل توسط شرکت‌های مختلف، بالاخره طلسم چند ده ساله‌ی اوراکل شکسته شده و اگرچه اوراکل نسبت به سال قبل رشد داشته است، ولی sql server مایکروسافت توانسته، هم در قابلیت اجرای فعلی و هم در بصیرت و آینده نگری بالاتر از محصول شرکت اوراکل بایستد. بنابراین عملا دوران پادشاهی مطلق اوراکل در حوزه‌ی پایگاه‌های داده‌ی عملیاتی به سر رسیده است.

در انتها لازم می‌بینم به نکاتی مهم اشاره کنم:
- شرکت اوراکل بر خلاف تصور خیلی از افراد، همانند شرکت‌های مایکروسافت، آی بی ام و ... محصولات گسترده و مختلفی دارد و این بررسی و نتایج تنها در حوزه‌ی سیستم‌های مدیریت پایگاه داده عملیاتی بود.
- بالاتر بودن sql server مایکروسافت از اوراکل در سال 2015 به این معنا نیست که اوراکل نمی‌تواند به جایگاه قبلی خود برگردد؛ بلکه شاید در سال‌های آینده این رتبه بندی باز هم تغییر کند. در واقع این گزارش به این معنا است که فاصله‌ی زیاد قدیم بین sql server و oracle db از بین رفته و در حال حاضر این دو به رقیب سر سختی برای یکدیگر تبدیل شده‌اند.
- وجود رقابت نزدیک بین شرکت‌های بزرگ باعث می‌شود که این شرکت‌ها حداکثر تلاش خود را برای بهتر کردن محصولات خود انجام بدهند و برندگان اصلی این وضعیت، استفاده کنندگان از این محصولات هستند.
- بنده به عنوان نگارنده‌ی این پست شخصا با هر دو محصول oracle db و sql server کار می‌کنم و تلاش کردم که این پست بی طرفانه باشد؛ پس لطفا متعصبانه قضاوت نکنید.
مطالب
پیاده سازی INotifyPropertyChanged با استفاده از Unity Container
AOP یکی از فناوری‌های مرتبط با توسعه نرم افزار محسوب می‌شود که توسط آن می‌توان اعمال مشترک و متداول موجود در برنامه را در یک یا چند ماژول مختلف قرار داد (که به آن‌ها Aspects نیز گفته می‌شود) و سپس آن‌ها را به مکان‌های مختلفی در برنامه متصل ساخت. عموما Aspects، قابلیت‌هایی را که قسمت عمده‌ای از برنامه را تحت پوشش قرار می‌دهند، کپسوله می‌کنند. اصطلاحا به این نوع قابلیت‌های مشترک، تکراری و پراکنده مورد نیاز در قسمت‌های مختلف برنامه، Cross cutting concerns نیز گفته می‌شود؛ مانند اعمال ثبت وقایع سیستم، امنیت، مدیریت تراکنش‌ها و امثال آن. با قرار دادن این نیازها در Aspects مجزا، می‌توان برنامه‌ای را تشکیل داد که از کدهای تکراری عاری است.

پیاده سازی INotifyPropertyChanged یکی از این مسائل می‌باشد که می‌توان آن را در یک Aspect محصور و در ماژول‌های مختلف استفاده کرد.

مسئله:
کلاس زیر مفروض است:
public class Foo
{
        public virtual int Id { get; set; }

        public virtual string Name { get; set; }
}
اکنون می‌خواهیم  کلاس Foo را به INotifyPropertyChanged مزین، و  یک Subscriber به قسمت set پراپرتی‌های کلاس‌ تزریق کنیم.

راه حل:
ابتدا پکیچ‌های Unity را از Nuget دریافت کنید:
PM> Install-Package Unity.Interception
این پکیچ وابستگی‌های خود را که Unity و CommonServiceLocator هستند نیز دریافت می‌کند.

حال یک Interceptor که اینترفیس IInterceptionBehavior را پیاده سازی می‌کند، می‌نویسیم:
namespace NotifyPropertyChangedInterceptor.Interceptions
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Reflection;
    using Microsoft.Practices.Unity.InterceptionExtension;

    class NotifyPropertyChangedBehavior : IInterceptionBehavior
    {
        private event PropertyChangedEventHandler PropertyChanged;

        private readonly MethodInfo _addEventMethodInfo =
            typeof(INotifyPropertyChanged).GetEvent("PropertyChanged").GetAddMethod();

        private readonly MethodInfo _removeEventMethodInfo =
            typeof(INotifyPropertyChanged).GetEvent("PropertyChanged").GetRemoveMethod();

        
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            if (input.MethodBase == _addEventMethodInfo)
            {
                return AddEventSubscription(input);
            }

            if (input.MethodBase == _removeEventMethodInfo)
            {
                return RemoveEventSubscription(input);
            }
            
            if (IsPropertySetter(input))
            {
                return InterceptPropertySet(input, getNext);
            }
            
            return getNext()(input, getNext);
        }

        public bool WillExecute
        {
            get { return true; }
        }

        public IEnumerable<Type> GetRequiredInterfaces()
        {
            yield return typeof(INotifyPropertyChanged);
        }

        private IMethodReturn AddEventSubscription(IMethodInvocation input)
        {
            var subscriber = (PropertyChangedEventHandler)input.Arguments[0];
            PropertyChanged += subscriber;

            return input.CreateMethodReturn(null);
        }

        private IMethodReturn RemoveEventSubscription(IMethodInvocation input)
        {
            var subscriber = (PropertyChangedEventHandler)input.Arguments[0];
            PropertyChanged -= subscriber;

            return input.CreateMethodReturn(null);
        }

        private bool IsPropertySetter(IMethodInvocation input)
        {
            return input.MethodBase.IsSpecialName && input.MethodBase.Name.StartsWith("set_");
        }

        private IMethodReturn InterceptPropertySet(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            var propertyName = input.MethodBase.Name.Substring(4);

            var subscribers = PropertyChanged;
            if (subscribers != null)
            {
                subscribers(input.Target, new PropertyChangedEventArgs(propertyName));
            }

            return getNext()(input, getNext);
        }
    }
}

متد Invoke : این متد Behavior مورد نظر را پردازش می‌کند (در اینجا، تزریق یک Subscriber در قسمت set پراپرتی ها).
متد GetRequiredInterfaces : یک روش است برای یافتن کلاس هایی که با اینترفیس IInterceptionBehavior مزین شده‌اند.
پراپرتی WillExecute : ابن پراپرتی به Unity می‌گوید که این Behavior اعمال شود یا نه. اگر مقدار برگشتی آن false باشد، متد Invoke اجرا نخواهد شد.
همانطور که در متد Invoke مشاهد می‌کنید، شرط هایی برای افزودن و حذف یک  Subscriber و چک کردن متد set نوشته شده و در غیر این صورت کنترل به متد بعدی داده می‌شود.

اتصال Interceptor به کلاس ها
در ادامه Unity را برای ساخت یک نمونه از کلاس پیکربندی می‌کنیم:
var container = new UnityContainer();

container.RegisterType<Foo, Foo>(
                new AdditionalInterface<INotifyPropertyChanged>(),
                new Interceptor<VirtualMethodInterceptor>(),
                new InterceptionBehavior<NotifyPropertyChangedBehavior>())
                .AddNewExtension<Interception>();
توسط متد RegisterType یک Type را با پیکربندی دلخواه به Unity معرفی می‌کنیم. در اینجا به ازای درخواست Foo (اولین پارامتر جنریک)، یک Foo (دومین پارامتر جنریک ) برگشت داده می‌شود. این متد تعدادی InjetctionMember (بصورت params) دریافت می‌کند که در این مثال سه InjetctionMember  به آن پاس داه شده است:
  • Interceptor : اطلاعاتی در مورد IInterceptor و نحوه‌ی Intercept یک شیء را نگه داری می‌کند. در اینجا از  VirtualMethodInterceptor برای تزریق کد استفاده شده.
  • InterceptionBehavior : این کلاس Behavior مورد نظر را به کلاس تزریق می‌کند.
  • AddintionalInterface  : کلاس target را مجبور به پیاده سازی اینترفیس دریافتی از پارامتر می‌کند.  اگر کلاس behavior، متد  GetRequiredInterfaces  اینترفیس INotifyPropertyChanged را برمی گرداند، نیازی نیست از AddintionalInterface در پارامتر متد فوق استفاده کنید. 

نکته :
کلاس VirtualMethodInterceptor فقط اعضای virtual را تحت تاثیر قرار می‌دهد.
اکنون نحوه‌ی ساخت یک نمونه از کلاس Foo به شکل زیر است:
var foo = container.Resolve<Foo>();
(foo as INotifyPropertyChanged).PropertyChanged += FooPropertyChanged;
private void FooPropertyChanged (object sender, PropertyChangedEventArgs e)
 {
      // Do some things.......
 }

نکته‌ی تکمیلی
طبق مستندات MSDN، کلاس VirtualMethodInterceptor  یک کلاس جدید مشتق شده از کلاس target (در اینجا Foo) می‌سازد. بنابراین اگر کلاس‌های شما دارای Data annotation و یا در کلاس‌های Mapper یک ORM استفاده شده‌اند (مانند کلاس‌های لایه Domain)، بجای  VirtualMethodInterceptor  از TransparentProxyInterceptor استفاده کنید.
سرعت اجرای VirtualMethodInterceptor سریعتر است ؛ اما به یاد داشته که برای استفاده از  TransparentProxyInterceptor  باید کلاس target از کلاس MarshalByRefObject ارث بری کند.
مطالب
PowerShell 7.x - قسمت چهارم - نوشتن اولین اسکریپت
دستوراتی که درون کنسول مینویسیم، تک خطی یا one-linear هستند؛ هر چند میتوان با زدن کلیدهای Shift + Enter دستورات چندخطی هم نوشت یا حتی با گذاشتن semicolon بعد از هر دستور میتوانیم دریک خط چندین دستور را پشت‌سر هم بنویسیم. اما برای نوشتن دستورات طولانی‌تر بهتر است دستورات را درون فایل‌های جدایی قرار دهیم و از VSCode یا PowerShell ISE (فقط در ویندوز) نیز برای نوشتن اسکریپت‌ها استفاده کرد. اسکریپت‌های PowerShell با پسوند ps1 و psm1 (برای نوشتن ماژول) هستند؛ هر چند چندین پسوند دیگر نیز برای فایل‌های PowerShell وجود دارند که در اینجا میتوانید لیست آنها را مشاهده کنید. درون یک فایل ps1 امکان نوشتن و ترکیب دستورات مختلف را داریم. همچنین میتوانیم از امکانات زبان سی‌شارپ هم استفاده کنیم؛ زیرا PowerShell در واقع اپلیکیشنی است که توسط NET Core. و با زبان #C نوشته شده‌است. در نتیجه میتوانیم بگوئیم زبان اسکریپتی که در PowerShell استفاده میشود، یک DSL برای زبان #C است. در PowerShell همه چیز یک آبجکت محسوب میشود. برای تست این مورد میتوانید درون کنسول PowerShell دستور زیر را وارد کنید:
PS> "" | Get-Member
دستور فوق یک لیست از تمامی توابع و پراپرتی‌های نوع System.String را نمایش خواهد داد:
   TypeName: System.String

Name                 MemberType            Definition
----                 ----------            ----------
Clone                Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo            Method                int CompareTo(System.Object value), int CompareTo(strin…
Contains             Method                bool Contains(string value), bool Contains(string value…
CopyTo               Method                void CopyTo(int sourceIndex, char[] destination, int de…
EndsWith             Method                bool EndsWith(string value), bool EndsWith(string value…
EnumerateRunes       Method                System.Text.StringRuneEnumerator EnumerateRunes()
Equals               Method                bool Equals(System.Object obj), bool Equals(string valu…
GetEnumerator        Method                System.CharEnumerator GetEnumerator(), System.Collectio…
GetHashCode          Method                int GetHashCode(), int GetHashCode(System.StringCompari…
GetPinnableReference Method                System.Char&, System.Private.CoreLib, Version=6.0.0.0, …
GetType              Method                type GetType()
GetTypeCode          Method                System.TypeCode GetTypeCode(), System.TypeCode IConvert…
IndexOf              Method                int IndexOf(char value), int IndexOf(char value, int st…
IndexOfAny           Method                int IndexOfAny(char[] anyOf), int IndexOfAny(char[] any…
Insert               Method                string Insert(int startIndex, string value)
IsNormalized         Method                bool IsNormalized(), bool IsNormalized(System.Text.Norm…
LastIndexOf          Method                int LastIndexOf(string value, int startIndex), int Last…
LastIndexOfAny       Method                int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(ch…
Normalize            Method                string Normalize(), string Normalize(System.Text.Normal…
PadLeft              Method                string PadLeft(int totalWidth), string PadLeft(int tota…
PadRight             Method                string PadRight(int totalWidth), string PadRight(int to…
Remove               Method                string Remove(int startIndex, int count), string Remove…
Replace              Method                string Replace(string oldValue, string newValue, bool i…
ReplaceLineEndings   Method                string ReplaceLineEndings(), string ReplaceLineEndings(…
Split                Method                string[] Split(char separator, System.StringSplitOption…
StartsWith           Method                bool StartsWith(string value), bool StartsWith(string v…
Substring            Method                string Substring(int startIndex), string Substring(int …
ToBoolean            Method                bool IConvertible.ToBoolean(System.IFormatProvider prov…
ToByte               Method                byte IConvertible.ToByte(System.IFormatProvider provide…
ToChar               Method                char IConvertible.ToChar(System.IFormatProvider provide…
ToCharArray          Method                char[] ToCharArray(), char[] ToCharArray(int startIndex…
ToDateTime           Method                datetime IConvertible.ToDateTime(System.IFormatProvider…
ToDecimal            Method                decimal IConvertible.ToDecimal(System.IFormatProvider p…
ToDouble             Method                double IConvertible.ToDouble(System.IFormatProvider pro…
ToInt16              Method                short IConvertible.ToInt16(System.IFormatProvider provi…
ToInt32              Method                int IConvertible.ToInt32(System.IFormatProvider provide…
ToInt64              Method                long IConvertible.ToInt64(System.IFormatProvider provid…
ToLower              Method                string ToLower(), string ToLower(cultureinfo culture)
ToLowerInvariant     Method                string ToLowerInvariant()
ToSByte              Method                sbyte IConvertible.ToSByte(System.IFormatProvider provi…
ToSingle             Method                float IConvertible.ToSingle(System.IFormatProvider prov…
ToString             Method                string ToString(), string ToString(System.IFormatProvid…
ToType               Method                System.Object IConvertible.ToType(type conversionType, …
ToUInt16             Method                ushort IConvertible.ToUInt16(System.IFormatProvider pro…
ToUInt32             Method                uint IConvertible.ToUInt32(System.IFormatProvider provi…
ToUInt64             Method                ulong IConvertible.ToUInt64(System.IFormatProvider prov…
ToUpper              Method                string ToUpper(), string ToUpper(cultureinfo culture)
ToUpperInvariant     Method                string ToUpperInvariant()
Trim                 Method                string Trim(), string Trim(char trimChar), string Trim(…
TrimEnd              Method                string TrimEnd(), string TrimEnd(char trimChar), string…
TrimStart            Method                string TrimStart(), string TrimStart(char trimChar), st…
TryCopyTo            Method                bool TryCopyTo(System.Span[char] destination)
Chars                ParameterizedProperty char Chars(int index) {get;}
Length               Property              int Length {get;}
در واقع میتوانیم بگوئیم هرچیزی در PowerShell یک آبجکت NET. است. در ادامه لیستی را از قابلیت‌های PowerShell به عنوان یک زبان اسکریپتی، بررسی خواهیم کرد.

تعریف متغیر
برای تعریف یک متغیر از علامت $ قبل از نام متغیر استفاده میکنیم. نوع متغیر نیز براساس مقداری که به آن انتساب داده میشود، تعیین خواهد شد: 
$stringVariable = "Hello World"
$letter = 'A'
$isEnabled = $false
$age = 33
$height = 76
$doubleVar = 54321.21
$singleVar = 76549.11
$longVar = 2382.22
$dateVar = "July 24, 1986"
$arrayVar = "A", "B", "C"
$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
همچنین میتوانیم نوع متغیر را نیز به صورت صریح تعیین کنیم: 
[string]$stringVariable = "Hello World"
[char]$letter = 'A'
[bool]$isEnabled = $false
[int]$age = 33
[decimal]$height = 76
[double]$doubleVar = 54321.21
[single]$singleVar = 76549.11
[long]$longVar = 2382.22
[DateTime]$dateVar = "July 24, 1986"
[array]$arrayVar = "A", "B", "C"
[hashtable]$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
لازم به ذکر است scope متغیرها در حالت پیش‌فرض به local تنظیم میشود. به این معنا که در جایی که تعریف میشوند، قابل دسترسی خواهند بود. قاعدتاً اگر متغیرها را در ابتدای اسکریپت تعریف کنید، میدان دید آن به صورت سراسری خواهد بود و در هرجایی از کد در دسترس خواهند بود. اما برای اینکه به صورت صریح یک متغیر را به صورت سراسری تعریف کنیم میتوانیم از کلمه‌کلیدی global بعد از تعیین نوع متغیر و قبل از علامت $ استفاده کنیم: 
$global:stringVariable = "Hello World"

// Or

[string]$global:stringVariable = "Hello World"

عبارات شرطی
همانند دیگر زبان‌های اسکریپتی، در PowerShell نیز قابلیت تعریف ساختارهای شرطی وجود دارد: 
$guess = 20
switch ($guess) {
    {$_ -eq 20} { Write-Host "You guessed right!" }
    {$_ -gt 20} { Write-Host "You guessed too high!" }
    {$_ -lt 20} { Write-Host "You guessed too low!" }
    default { Write-Host "You didn't guess a number!" }
}

$guess = 20
if ($guess -eq 20) { 
    Write-Host "You guessed right!" 
}
elseif ($guess -gt 20) { 
    Write-Host "You guessed too high!" 
}
elseif ($guess -lt 20) { 
    Write-Host "You guessed too low!" 
}
else { 
  Write-Host "You didn't guess a number!"
}
همانطور که مشاهده میکنید از یکسری اپراتور برای بررسی شرط‌ها استفاده شده است. در اینجا میتوانید لیست کامل آنها را مشاهده کنید. لازم به ذکر است که از PowerShell 7.0 به بعد نیز Ternary Operator اضافه شده است: 
$message = (Test-Path $path) ? "Path exists" : "Path not found"

حلقه‌ها
همچنین از حالت‌های مختلف loop نیز پشتیبانی میشود: 
[int]$num = 10
for ($i = 1; $i -le $num; $i++) {
    Write-Host "`n"

    for ($j = 1; $j -le $num; $j++) {
        Write-Host -NoNewline -ForegroundColor Green ($i * $j).ToString().PadLeft(6)
    }
}
Write-Host "`n`n"

[int]$counter = 1
while ($counter -le 10) {
    Write-Host "Hello World"
    $counter++
}

do {
    Write-Host "Hello World"
    $counter++
} while ($counter -le 10)

foreach ($i in 1..10) {
    Write-Host "Hello World"
}

$items = "Hello", "World"
$items | ForEach-Object {
    Write-Host $_
}

لازم به ذکر است برای ForEach-Object از % نیز میتوانید استفاده کنید. اما بطور کلی بهتر است تا حد امکان از aliaseها استفاده نکنید؛ زیرا خوانایی کد را مقداری سخت میکند. این مورد توسط خود VSCode هم هشدار داده میشود: 


آرایه‌ها
در PowerShell به صورت پیش‌فرض آرایه‌ها از نوع []System.Object در نظر گرفته میشوند. در اینجا نیز آرایه‌ها immutable هستند. چندین روش برای ایجاد آرایه وجود دارد. در ادامه یک آرایه خالی را تعریف کرده‌ایم: 
$array = @()
یک روش دیگر تعریف آرایه اینگونه است: 
$myArray = [Object[]]::new(10)
$byteArray = [Byte[]]::new(100)
$ipAddresses = [IPAddress[]]::new(5)

$mylist = [System.Collections.Generic.List[int]]::new()
از PowerShell 5.0 به بعد میتوانیم سینتکس namespaceها را با کمک using خلاصه‌تر بنویسیم:
using namespace System.Collections.Generic
$mylist = [List[int]]::new()
از range operator هم میتوانیم برای مقداردهی یک آرایه استفاده کرد: 
$numbers = 1..10
$alphabet = "a".."z"

foreach ($item in 1..10) {
    Write-Host "Hello World"
}
برای دسترسی به عناصر یک آرایه نیز میتوانیم از range operator استفاده کنیم: 
$numbers = 1..10

$numbers
Write-Output "".PadRight(10, "-")
$numbers[1..2 + 2..4]
همانطور که مشاهده میکنید از اپراتور + برای append کردن یک بازه از اعداد، به آرایه استفاده کرده‌ایم. دقت داشته باشید که با هر بار اضافه/حذف آیتم‌ها، یک آرایه جدید ساخته میشود. این مورد در آرایه‌هایی با سایز بزرگ میتواند مشکل کارآیی ایجاد کند. بنابراین اگر عملیات اضافه کردن و حذف کردن از یک آرایه را زیاد انجام میدهید، بهتر است از ArrayList استفاده کنید. تفاوت آن نیز این است که برخلاف آرایه‌های عادی، سایز ArrayList ثابت نیست. تعریف ArrayList نیز اینگونه است: 
$colours = [System.Collections.ArrayList]@("Red", "Green", "Blue")
$colours.Add("Yellow")
$colours.Remove("Red")
از دیگر کالکشن‌های NET. نیز میتوانید استفاده کنید؛ به عنوان مثال در مثال زیر از List برای اضافه کردن ده هزار آیتم استفاده کرده‌ایم:
using namespace System.Collections.Generic

$mylist = [List[int]]::new()
Measure-Command { 1..100000 | ForEach-Object { $mylist.Add($_) } }
Measure-Command { $mylist.Where({ $_ -gt 10000 }) }
Measure-Command { $mylist.Contains(10000) }
نکته: کد فوق در حالت استفاده از ArrayList مقداری کندتر است. دلیل آن نیز این است که ArrayList امکان اضافه کردن هر آبجکتی را به ما میدهد. در نتیجه موقع جستجو باید یکبار عملیات unboxing را برای تشخیص نوع درخواست شده انجام دهد.

Hashtable
ساختار دیگری که میتوانید استفاده کنید Hash Tableها هستند؛ از این ساختار برای ایجاد custom objects و همچنین پاس دادن پارامترها به یک command استفاده میشود:
$sensors = @{
    tempreture = "Temperature"
    humidity   = "Humidity"
    pressure   = "Pressure"
    light      = "Light"
    noise      = "Noise"
    co2        = "CO2"
    battery    = "Battery"
    min_temp   = "Min Temp"
    max_temp   = "Max Temp"
}
با دستور زیر میتوانید لیستی از Commandهایی که ورودی‌شان از نوع Hash table است را مشاهده کنید:
PS> Get-Command -ParameterType Hashtable


CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        TabExpansion2                                                 
Cmdlet          Add-Member                                         7.0.0.0    Microsoft.Powe…
Cmdlet          ConvertTo-Html                                     7.0.0.0    Microsoft.Powe…
Cmdlet          Get-Job                                            7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-Command                                     7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-RestMethod                                  7.0.0.0    Microsoft.Powe…
Cmdlet          Invoke-WebRequest                                  7.0.0.0    Microsoft.Powe…
Cmdlet          New-Object                                         7.0.0.0    Microsoft.Powe…
Cmdlet          New-PSRoleCapabilityFile                           7.2.7.500  Microsoft.Powe…
Cmdlet          New-PSSession                                      7.2.7.500  Microsoft.Powe…
Cmdlet          Remove-Job                                         7.2.7.500  Microsoft.Powe…
Cmdlet          Select-Xml                                         7.0.0.0    Microsoft.Powe…
Cmdlet          Set-PSReadLineOption                               2.1.0      PSReadLine
Cmdlet          Stop-Job                                           7.2.7.500  Microsoft.Powe…
Cmdlet          Wait-Job                                           7.2.7.500  Microsoft.Powe…

توابع
برای ایجاد توابع در PowerShell میتوانید از سینتکس زیر استفاده کنید:
function Write-HelloWorld {
    Write-Host "Hello World"
}
برای نامگذاری توابع بهتر است از قالب verb-noun استفاده کنید. برای قسمت verb نیز بهتر است از یکسری افعال تائیدشده (approved verbs) که مستندات مایکروسافت پیشنهاد میدهد استفاده کنید (+) در این زمینه VSCode در صورت انتخاب یک نام نامناسب به شما هشدار خواهد:

تفاوت بین Function و Cmdlet چیست؟
توابع و cmdletها عملاً از لحاظ کاربرد باهم تفاوتی ندارند. تنها تفاوت آنها در نحوه ساخت‌شان است. cmdletها با #C ساخته میشوند. به این معنا که یکسری DLL کامپایل شده هستند که در نهایت از آنها استفاده خواهیم کرد. اما توابع در PowerShell با کمک سینتکسی که اشاره شد ایجاد میشوند. توسط دستور زیر میتوانیم لیستی از توابعی را که درون سشن PowerShellمان بارگذاری شده‌اند، ببینیم: 
PS> Get-Command -CommandType Function

در PowerShell نیز توابع قابلیت دریافت ورودی را نیز دارند. در ساده‌ترین حالت میتوانیم از آرایه args$ استفاده کنیم؛ دقیقاً چیزی مشابه arguments در JavaScript است:
function Write-HelloWorld {
    Write-Host "First Argument: $($args[0])"
    Write-Host "Second Argument: $($args[1])"
    Write-Host "Third Argument: $($args[2])"
    Write-Host "Fourth Argument: $($args[3])"
}
به این حالت Positional Parameters گفته میشود. یک روش دیگر تعریف پارامتر استفاده از Named Parameters است:
function Write-HelloWorld(
    [string]$first,
    [string]$second,
    [string]$third,
    [string]$fourth
) {
    Write-Host "First Argument: $($first)"
    Write-Host "Second Argument: $($second)"
    Write-Host "Third Argument: $($third)"
    Write-Host "Fourth Argument: $($fourth)"
}

در قسمت بعد در مورد Advanced Functionها صحبت خواهیم کرد و اجزای دیگر توابع را بیشتر توضیح خواهیم داد.

یک نکته در مورد خروجی دستورات درون کنسول
در ویندوز میتوانیم خروجی کنسول را به Out-GridView پایپ کنیم که در واقع یک GUI برای نمایش دادن خروجی کنسول است. این کامند فقط در ویندوز قابل استفاده است:

یک نسخه cross-platform آن نیز که مناسب کنسول است تهیه شده که میتوانید از آن استفاده کنید: 
Get-Command -ParameterType Hashtable | Out-ConsoleGridview
با این خروجی:

البته به صورت پیش‌فرض نصب نیست و باید از طریق PowerShell Gallery آن را نصب کنید:

Install-Module -Name Microsoft.PowerShell.ConsoleGuiTools


اشتراک‌ها
ایجاد درخواستهای ajax در mvc و بررسی نکات کاربردی

بررسی نکات کاربردی استفاده از  ajax در mvc  از جمله بررسی مقادیر برگشتی از این درخواستها و نکته انتقال کاربر به کنترلر جدید در هنگام موفقیت آمیز بودن اجرای درخواست اجاکسی که توسط دستور window.location قابل انجام است .و برای ارسال پارامتر نیز میتوان از tempdata استفاده کرد .

ایجاد درخواستهای ajax در mvc و بررسی نکات کاربردی
نظرات مطالب
استفاده از قابلیت Speech Recognition ویندوز 7 برای تولید زیرنویس انگلیسی
فایل wave مورد نظر برای برنامه SubtitleTools را میتوان توسط کتابخانه FFMPEG با اجرای دستور زیر استخراج کرد:
// ffmpeg -i e:\inputfile.MP4 -ac 1 -b:a 80k -sample_rate 22050 e:\output.wav

مطالب
1# آموزش سیستم مدیریت کد Git
ضرورت استفاده از یک سیستم کنترل نسخه:


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

آشنایی با Git:

Git توسط سازنده سیستم عامل لینوکس یعنی آقای Linus Torvalds و برای مدیریت کد‌های آن ساخته شد که بعدها توسط Linux-BitKeeper ارتقا  یافت. BitKeeper یک سیستم مدیریت کد توزیع شده است که البته رایگان نیست. تیم BitKeeper در ابتدا پروژه لینوکس را به صورت رایگان پشتیبانی می‌کرد اما در سال 2005 این حمایت را قطع کرد. در این هنگام تیم توسعه لینوکس تصمیم گرفت که خود یک سیستم مدیریت کد توزیع شده ایجاد کند. آن‌ها این سیستم را با Perl و C نوشتند و آن را برای اجرا شدن بر روی انواع سیستم عامل‌ها نظیر لینوکس ویندوز و حتی مک آماده کردند اهداف اصلی Git عبارتند از:
1) سرعت بالا
2) سادگی
3) قدرت پشتیبانی بالا از Merge/Branching
4) یک سیستم کاملا توزیع شده
5) قابلیت توسعه برای پروژه‌های بزرگ

تفاوت سیستم‌های متمرکز و توزیع شده:

سیستم‌های کنترل نسخه را می‌توان بر اساس خصوصیات مختلف در دسته‌های متفاوتی قرار داد اما از نظر معماری سیستم, به دو دسته‌ی زیر تقسیم می‌شوند :
۱) (VCS (Version Control System –سیستم‌های مدیریت نسخه متمرکز
۲) (DVCS (Distributed Version Control System- سیستم‌های مدیریت نسخه توزیع شده
در ادامه مقاله تفاوت این دو روش را بیان خواهیم نمود و به بررسی مزایا و معایب آن‌ها خواهیم پرداخت

تعریف Repository:

مخزن یا همان Repository محلی است که یک سیستم مدیریت نسخه از آن برای نگهداری تغییرات فایل‌ها استفاده می‌کند. در سیستم‌های VCS این مخزن به صورت متمرکز یا اصطلاحا Centralized Repository می‌باشد. به این معنا که یک Repository بر روی یک ماشین، خواه سیستم خود برنامه نویس(در پروژه‌های انفرادی) و خواه یک سرور قرار دارد (در پروژه‌های تیمی) و برنامه نویسان تغییرات فایل‌های خود را به سمت این سرور می‌فرستند و این سرور وظیفه نگهداری تمامی نسخه‌ها و اطلاعات مربوطه از برنامه نویسان مختلف را به عهده دارد. اشکال این روش در این است که برنامه نویس تنها به نسخه جاری که بر روی سیستم خود است دسترسی دارد و اگر بنا به دلیلی بخواهد از نسخه‌های پیشین استفاده کند باید آن را از سرور بخواهد که این کار مشکل دیگری ایجاد می‌کند و آن این است که ممکن است برنامه نویس همیشه در موقعیتی نباشد که بتواند به سرور دسترسی داشته باشد. به همین دلیل این روش وابستگی زیادی برای برنامه نویس ایجاد می‌کند اما پیاده سازی این روش آسان‌تر از مدل توزیع شده است.
در مدل توزیع شده  علاوه بر یک مخزن که بر روی یک سرور قرار داد و تمامی نسخه‌ها در آن جا نگهداری می‌شود، هر برنامه نویس یک نسخه محلی مخزن را نیز در اختیار دارد. به این ترتیب وابستگی برنامه نویس به سرور کاهش می‌یابد؛ همچنین می‌توان با ایجاد SubRepository‌ها یک ساختار درختی ایجاد نمود که هر کدام از این زیر سیستم‌ها در نهایت اطلاعات را در سرور اصلی قرار می‌دهند. علاوه بر این به دلیل ساختار توزیع شده، امکان بک آپ گیری در این روش مطمئن‌تر است. زیرا تنها وابسته به یک سرور نیست و می‌تواند بر روی سیستم‌های مختلف توزیع شده باشد. البته از اشکالات این روش پیچیدگی پیاده سازی بیشتر آن نسبت به سیستم‌های متمرکز است.

اما سوال این جا است که ما حقیقتا چه چیزی را باید ذخیره کنیم ؟

پاسخ به این سوال بسیار ساده است: هر آنچه برای ما مهم است که این شامل فایل‌های کد, فایل‌های پیکربندی,  خروجی‌های نظیر dll و غیره است. البته در این بین استثنائاتی نظیر فایل‌های EXE و یا پکیج‌های نصب شده  وجود دارد که در بسیاری از موارد نیازی به پیگیری نسخه‌های آن‌ها نیست اما تمامی این‌ها وابسته به نظر برنامه نویس است.

در ادامه مقالات ما به تعاریف مورد نیاز در سیستم‌های مدیرت کد, ساختار Git و چگونگی نصب و استفاده آن خواهیم پرداخت.


 
نظرات مطالب
غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن
البته مشکل عدم رمزگشایی بعد از ریست شدن سرور مختص ویندوز نیست. در حالت پیش فرض محل ذخیره کلیدهای رمزنگاری تولید شده در حافظه است و همانطور که اشاره کردید باید با فراخوانی متد PersistKeysToFileSystem محلی را برای ذخیره سازی دائمی آنها تدارک دید. این محل میتواند پوشه ای در هاست شما باشد. (با توجه به عدم پشتیبانی از دات نت core توسط سرویس دهندگان در حال حاضر) و همچنین میتوان با پیاده‌سازی سفارشی از واسطهای IXmlDecryptor و IXmlEncryptor  و تزریق آنها به سیستم از یک Certificate غیرمعتبر استفاده کرد و نیازی به ثبت آن در Root store نیست. 
نظر شخصی بنده اینست که کلاسهای پیاده سازی شده برای رمزنگاری و رمزگشایی به شدت نگاه امنیتی بالایی را تدارک دیده است که در بیشتر سناریوها واقعا نیازی به اینهمه سطح از پیچیدگی وجود ندارد. در واقع میتوان با پیاده سازی واسط IDataProtectionProvider و تزریق آن به سیستم از روش رمزنگاری و رمزگشایی دلخواهی استفاده کرد.
مطالب
ساخت یک بارکدخوان با استفاده از OpenCV و ZXing.Net
فرض کنید می‌خواهیم بارکد این قبض را یافته و سپس عدد متناظر با آن‌را در برنامه بخوانیم.


مراحل کار به این صورت هستند:


بارگذاری تصویر و چرخش آن در صورت نیاز

ابتدا تصویر بارکد دار را بارگذاری کرده و آن‌را تبدیل به یک تصویر سیاه و سفید می‌کنیم:
// load the image and convert it to grayscale
var image = new Mat(fileName);
 
if (rotation != 0)
{
    rotateImage(image, image, rotation, 1);
}
 
if (debug)
{
    Cv2.ImShow("Source", image);
    Cv2.WaitKey(1); // do events
}
 
var gray = new Mat();
var channels = image.Channels();
if (channels > 1)
{
    Cv2.CvtColor(image, gray, ColorConversion.BgrToGray);
}
else
{
    image.CopyTo(gray);
}
در این بین ممکن است بارکد موجود در تصویر، دقیقا در زاویه‌ای که در تصویر ابتدای بحث قرار گرفته‌است، وجود نداشته باشد؛ مثلا منهای 90 درجه، چرخیده باشد. به همین جهت می‌توان از متد چرخش تصویر مطلب «تغییر اندازه، و چرخش تصاویر» ارائه شده در قسمت نهم این سری استفاده کرد.


تشخیص گرادیان‌های افقی و عمودی

یکی از روش‌های تشخیص بارکد، استفاده از روشی است که در تشخیص خودرو قسمت 16 بیان شد. تعداد زیادی تصویر بارکد را تهیه و سپس آن‌ها را به الگوریتم‌های machine learning جهت تشخیص و یافتن محدوده‌ی بارکد موجود در یک تصویر، ارسال کنیم. هرچند این روش جواب خواهد داد، اما در این مورد خاص، قسمت بارکد، شبیه به گرادیانی از رنگ‌ها است. کتابخانه‌ی OpenCV برای یافتن این نوع گرادیان‌ها دارای متدی است به نام Sobel :
// compute the Scharr gradient magnitude representation of the images
// in both the x and y direction
var gradX = new Mat();
Cv2.Sobel(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0, ksize: -1);
//Cv2.Scharr(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0);
 
var gradY = new Mat();
Cv2.Sobel(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1, ksize: -1);
//Cv2.Scharr(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1);
 
// subtract the y-gradient from the x-gradient
var gradient = new Mat();
Cv2.Subtract(gradX, gradY, gradient);
Cv2.ConvertScaleAbs(gradient, gradient);
 
if (debug)
{
    Cv2.ImShow("Gradient", gradient);
    Cv2.WaitKey(1); // do events
}


ابتدا درجه‌ی شدت گرادیان‌ها در جهت‌های x و y محاسبه می‌شوند. سپس این شدت‌ها از هم کم خواهند شد تا بیشترین شدت گرادیان موجود در محور x حاصل شود. این بیشترین شدت‌ها، بیانگر نواحی خواهند بود که احتمال وجود بارکدهای افقی در آن‌ها بیشتر است.


کاهش نویز و یکی کردن نواحی تشخیص داده شده

در ادامه می‌خواهیم با استفاده از متدهای تشخیص کانتور (قسمت 12)، نواحی با بیشترین شدت گرادیان افقی را پیدا کنیم. اما تصویر حاصل از قسمت قبل برای اینکار مناسب نیست. به همین جهت با استفاده از متدهای کار با مورفولوژی تصاویر، این نواحی گرادیانی را یکی می‌کنیم (قسمت 8).
// blur and threshold the image
var blurred = new Mat();
Cv2.Blur(gradient, blurred, new Size(9, 9));
 
var threshImage = new Mat();
Cv2.Threshold(blurred, threshImage, thresh, 255, ThresholdType.Binary);
 
if (debug)
{
    Cv2.ImShow("Thresh", threshImage);
    Cv2.WaitKey(1); // do events
}
 
 
// construct a closing kernel and apply it to the thresholded image
var kernel = Cv2.GetStructuringElement(StructuringElementShape.Rect, new Size(21, 7));
var closed = new Mat();
Cv2.MorphologyEx(threshImage, closed, MorphologyOperation.Close, kernel);
 
if (debug)
{
    Cv2.ImShow("Closed", closed);
    Cv2.WaitKey(1); // do events
}
 
 
// perform a series of erosions and dilations
Cv2.Erode(closed, closed, null, iterations: 4);
Cv2.Dilate(closed, closed, null, iterations: 4);
 
if (debug)
{
    Cv2.ImShow("Erode & Dilate", closed);
    Cv2.WaitKey(1); // do events
}
این سه مرحله را در تصاویر ذیل مشاهده می‌کنید:


ابتدا با استفاده از متد Threshold، تصویر را به یک تصویر باینری تبدیل خواهیم کرد. در این تصویر تمام نقاط دارای شدت رنگ کمتر از مقدار thresh، به مقدار حداکثر 255 تنظیم می‌شوند.
سپس با استفاده از متدهای تغییر مورفولوژی تصویر، قسمت‌های مجاور به هم را می‌بندیم و یکی می‌کنیم. این مورد در یافتن اشیاء احتمالی که ممکن است بارکد باشند، بسیار مفید است.
متدهای Erode و Dilate در اینجا کار حذف نویزهای اضافی را انجام می‌دهند؛ تا بهتر بتوان بر روی نواحی بزرگتر یافت شده، تمرکز کرد.



یافتن بزرگترین ناحیه‌ی به هم پیوسته‌ی موجود در یک تصویر

تمام این مراحل را انجام دادیم تا بتوانیم بزرگترین ناحیه‌ی به هم پیوسته‌ای را که احتمال می‌رود بارکد باشد، در تصویر تشخیص دهیم. پس از این آماده سازی‌ها، اکنون با استفاده از متد یافتن کانتورها، تمام نواحی یکی شده را یافته و بزرگترین مساحت ممکن را به عنوان بارکد انتخاب می‌کنیم:
//find the contours in the thresholded image, then sort the contours
//by their area, keeping only the largest one
 
Point[][] contours;
HiearchyIndex[] hierarchyIndexes;
Cv2.FindContours(
    closed,
    out contours,
    out hierarchyIndexes,
    mode: ContourRetrieval.CComp,
    method: ContourChain.ApproxSimple);
 
if (contours.Length == 0)
{
    throw new NotSupportedException("Couldn't find any object in the image.");
}
 
var contourIndex = 0;
var previousArea = 0;
var biggestContourRect = Cv2.BoundingRect(contours[0]);
while ((contourIndex >= 0))
{
    var contour = contours[contourIndex];
 
    var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour
    var boundingRectArea = boundingRect.Width * boundingRect.Height;
    if (boundingRectArea > previousArea)
    {
        biggestContourRect = boundingRect;
        previousArea = boundingRectArea;
    }
 
    contourIndex = hierarchyIndexes[contourIndex].Next;
}
 
 
var barcode = new Mat(image, biggestContourRect); //Crop the image
Cv2.CvtColor(barcode, barcode, ColorConversion.BgrToGray);
 
Cv2.ImShow("Barcode", barcode);
Cv2.WaitKey(1); // do events
حاصل این عملیات یافتن بزرگترین ناحیه‌ی گرادیانی به هم پیوسته‌ی موجود در تصویر است:


خواندن مقدار متناظر با بارکد یافت شده

خوب، تا اینجا موفق شدیم، محل قرارگیری بارکد را تصویر پیدا کنیم. مرحله‌ی بعد خواندن مقدار متناظر با این تصویر است. برای این منظور از کتابخانه‌ی سورس بازی به نام http://zxingnet.codeplex.com استفاده خواهیم کرد. این کتابخانه قادر است بارکد بسازد و همچنین تصاویر بارکدها را خوانده و مقادیر متناظر با آن‌ها را استخراج کند. برای نصب آن می‌توان از دستور ذیل استفاده کرد:
 PM> Install-Package ZXing.Net
پس از نصب این کتابخانه‌ی بارکدساز و بارکد خوان، اکنون تنها کاری که باید صورت گیرد، ارسال تصویر بارکد جدا شده‌ی توسط OpenCV به آن است:
private static string getBarcodeText(Mat barcode)
{
    // `ZXing.Net` needs a white space around the barcode
    var barcodeWithWhiteSpace = new Mat(new Size(barcode.Width + 30, barcode.Height + 30), MatType.CV_8U, Scalar.White);
    var drawingRect = new Rect(new Point(15, 15), new Size(barcode.Width, barcode.Height));
    var roi = barcodeWithWhiteSpace[drawingRect];
    barcode.CopyTo(roi);
 
    Cv2.ImShow("Enhanced Barcode", barcodeWithWhiteSpace);
    Cv2.WaitKey(1); // do events
 
    return decodeBarcodeText(barcodeWithWhiteSpace.ToBitmap());
}
 
private static string decodeBarcodeText(System.Drawing.Bitmap barcodeBitmap)
{
    var source = new BitmapLuminanceSource(barcodeBitmap);
 
    // using http://zxingnet.codeplex.com/
    // PM> Install-Package ZXing.Net
    var reader = new BarcodeReader(null, null, ls => new GlobalHistogramBinarizer(ls))
    {
        AutoRotate = true,
        TryInverted = true,
        Options = new DecodingOptions
        {
            TryHarder = true,
            //PureBarcode = true,
            /*PossibleFormats = new List<BarcodeFormat>
                    {
                        BarcodeFormat.CODE_128
                        //BarcodeFormat.EAN_8,
                        //BarcodeFormat.CODE_39,
                        //BarcodeFormat.UPC_A
                    }*/
        }
    };
 
    //var newhint = new KeyValuePair<DecodeHintType, object>(DecodeHintType.ALLOWED_EAN_EXTENSIONS, new Object());
    //reader.Options.Hints.Add(newhint);
 
    var result = reader.Decode(source);
    if (result == null)
    {
        Console.WriteLine("Decode failed.");
        return string.Empty;
    }
 
    Console.WriteLine("BarcodeFormat: {0}", result.BarcodeFormat);
    Console.WriteLine("Result: {0}", result.Text);
 
 
    var writer = new BarcodeWriter
    {
        Format = result.BarcodeFormat,
        Options = { Width = 200, Height = 50, Margin = 4},
        Renderer = new ZXing.Rendering.BitmapRenderer()
    };
    var barcodeImage = writer.Write(result.Text);
    Cv2.ImShow("BarcodeWriter", barcodeImage.ToMat());
 
    return result.Text;
}
چند نکته را باید در مورد کار با ZXing.Net بخاطر داشت؛ وگرنه جواب نمی‌گیرید:
الف) این کتابخانه حتما نیاز دارد تا تصویر بارکد، در یک حاشیه‌ی سفید در اختیار او قرار گیرد. به همین جهت در متد getBarcodeText، ابتدا تصویر بارکد یافت شده، به میانه‌ی یک مستطیل سفید رنگ بزرگ‌تر کپی می‌شود.
ب) برای تبدیل Mat به Bitmap مورد نیاز این کتابخانه می‌توان از متد الحاقی ToBitmap استفاده کرد (قسمت 7).
ج) پس از آن وهله‌ای از کلاس BarcodeReader آماده شده و در آن پارامترهایی مانند بیشتر سعی کن (TryHarder) و اصلاح درجه‌ی چرخش تصویر (AutoRotate) تنظیم شده‌اند.
د) بارکدهای موجود در قبض‌های ایران عموما بر اساس فرمت CODE_128 ساخته می‌شوند. بنابراین برای خواندن سریعتر آ‌نها می‌توان PossibleFormats را مقدار دهی کرد. اگر این مقدار دهی صورت نگیرد، تمام حالت‌های ممکن بررسی می‌شوند.

در آخر کار این متد، از متد Writer آن نیز برای تولید بارکد مشابهی استفاده شده‌است تا بتوان بررسی کرد این دو تا چه اندازه به هم شبیه هستند.


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


بهبود تصویر، پیش از ارسال آن به متد Decode کتابخانه‌ی ZXing.Net

در تصویر قبلی، سطر decode failed را هم ملاحظه می‌کنید. علت اینجا است که اولین سعی انجام شده، موفق نبوده است؛ چون تصویر تشخیص داده شده، بیش از اندازه نویز و حاشیه‌ی خاکستری دارد. می‌توان این حاشیه‌ی خاکستری را با دوبار اعمال متد Threshold از بین برد:
var barcodeClone = barcode.Clone();
var barcodeText = getBarcodeText(barcodeClone);
 
if (string.IsNullOrWhiteSpace(barcodeText))
{
    Console.WriteLine("Enhancing the barcode...");
    //Cv2.AdaptiveThreshold(barcode, barcode, 255,
        //AdaptiveThresholdType.GaussianC, ThresholdType.Binary, 9, 1);
    //var th = 119;
    var th = 100;
    Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.ToZero);
    Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.Binary);
    barcodeText = getBarcodeText(barcode);
}
 
Cv2.Rectangle(image,
    new Point(biggestContourRect.X, biggestContourRect.Y),
    new Point(biggestContourRect.X + biggestContourRect.Width, biggestContourRect.Y + biggestContourRect.Height),
    new Scalar(0, 255, 0),
    2);
 
if (debug)
{
    Cv2.ImShow("Segmented Source", image);
    Cv2.WaitKey(1); // do events
}
 
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();


اعداد یافت شده، دقیقا از روی تصویر بهبود یافته‌ی توسط متدهای Threshold خوانده شده‌اند و نه تصویر ابتدایی یافت شده. بنابراین به این موضوع نیز باید دقت داشت.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.