مطالب
استفاده از افزونه‌ی jsTree در ASP.NET MVC
jsTree  یکی از افزونه‌های بسیار محبوب jQuery جهت نمایش ساختارهای سلسله مراتبی، خود ارجاع دهنده و تو در تو است. روش ابتدایی استفاده از آن تعریف یک سری ul و li ثابت در صفحه و سپس فراخوانی این افزونه بر روی آن‌ها است که سبب نمایش درخت‌واره‌ا‌ی این اطلاعات خواهد شد. روش پیشرفته‌تر آن به همراه کار با داده‌های JSON و دریافت پویای اطلاعات از سرور است که در ادامه به بررسی آن خواهیم پرداخت.


دریافت افزونه‌ی jsTree

برای دریافت افزونه‌ی jsTree می‌توان به مخزن کد آن در Github مراجعه کرد و همچنین مستندات آن‌را در سایت jstree.com قابل مطالعه هستند.


تنظیمات مقدماتی jsTree

در این مطلب فرض شده‌است که فایل jstree.min.js، در پوشه‌ی Scripts و فایل‌های CSS آن در پوشه‌ی Content\themes\default کپی شده‌اند.
به این ترتیب layout برنامه چنین شکلی را خواهد یافت:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    
    <link href="~/Content/Site.css" rel="stylesheet" />
    <link href="~/Content/themes/default/style.min.css" rel="stylesheet" />
    <script src="~/Scripts/jquery.min.js"></script>
    <script src="~/Scripts/jstree.min.js"></script>
</head>
<body dir="rtl">
    @RenderBody()
    
    @RenderSection("scripts", required: false)
</body>
</html>

نمایش راست به چپ اطلاعات

در کدهای این افزونه به تگ body و ویژگی dir آن برای تشخیص راست به چپ بودن محیط دقت می‌شود. به همین جهت این تعریف را در layout فوق ملاحظه می‌کنید. برای مثال اگر به فایل jstree.contextmenu.js (موجود در مجموعه سورس‌های این افزونه) مراجعه کنید، یک چنین تعریفی قابل مشاهده است:
 right_to_left = $("body").css("direction") === "rtl";

تهیه ساختاری جهت ارائه‌ی خروجی JSON

با توجه به اینکه قصد داریم به صورت پویا با این افزونه کار کنیم، نیاز است بتوانیم ساختار سلسله مراتبی مدنظر را با فرمت JSON ارائه دهیم. در ادامه کلاس‌هایی که معادل فرمت JSON قابل قبول توسط این افزونه را تولید می‌کنند، ملاحظه می‌کنید:
using System.Collections.Generic;

namespace MvcJSTree.Models
{
    public class JsTreeNode
    {
        public string id { set; get; } // نام این خواص باید با مستندات هماهنگ باشد
        public string text { set; get; }
        public string icon { set; get; }
        public JsTreeNodeState state { set; get; }
        public List<JsTreeNode> children { set; get; }
        public JsTreeNodeLiAttributes li_attr { set; get; }
        public JsTreeNodeAAttributes a_attr { set; get; }

        public JsTreeNode()
        {
            state = new JsTreeNodeState();
            children = new List<JsTreeNode>();
            li_attr = new JsTreeNodeLiAttributes();
            a_attr = new JsTreeNodeAAttributes();
        }
    }

    public class JsTreeNodeAAttributes
    {
        // به هر تعداد و نام اختیاری می‌توان خاصیت تعریف کرد
        public string href { set; get; }
    }

    public class JsTreeNodeLiAttributes
    {
        // به هر تعداد و نام اختیاری می‌توان خاصیت تعریف کرد
        public string data { set; get; }
    }

    public class JsTreeNodeState
    {
        public bool opened { set; get; }
        public bool disabled { set; get; }
        public bool selected { set; get; }

        public JsTreeNodeState()
        {
            opened = true;
        }
    }
}
در اینجا به چند نکته باید دقت داشت:
- هر چند اسامی مانند a_attr، مطابق اصول نامگذاری دات نت نیستند، ولی این نام‌ها را تغییر ندهید. زیرا این افزونه دقیقا به همین نام‌ها و با همین املاء نیاز دارد.
- id، می‌تواند دقیقا معادل id یک رکورد در بانک اطلاعاتی باشد. Text عنوان گره‌ای (node) است که نمایش داده می‌شود. icon در اینجا مسیر یک فایل png است جهت نمایش در کنار عنوان هر گره. توسط state می‌توان مشخص کرد که زیر شاخه‌ی جاری به صورت باز نمایش داده شود یا بسته. به کمک خاصیت children می‌توان زیر شاخه‌ها را تا هر سطح و تعدادی که نیاز است تعریف نمود.
- خاصیت‌های li_attr و a_attr کاملا دلخواه هستند. برای مثال در اینجا دو خاصیت href و data را در کلاس‌های مرتبط با آن‌ها مشاهده می‌کنید. می‌توانید در اینجا به هر تعداد ویژگی سفارشی دیگری که جهت تعریف یک گره نیاز است، خاصیت اضافه کنید.

ساده‌ترین مثالی که از ساختار فوق می‌تواند استفاده کند، اکشن متد زیر است:
        [HttpPost]
        public ActionResult GetTreeJson()
        {
            var nodesList = new List<JsTreeNode>();

            var rootNode = new JsTreeNode
            {
                id = "dir",
                text = "Root 1",
                icon = Url.Content("~/Content/images/tree_icon.png"),
                a_attr = { href = "http://www.bing.com" }
            };
            nodesList.Add(rootNode);

            nodesList.Add(new JsTreeNode
            {
                id = "test1",
                text = "Root 2",
                icon = Url.Content("~/Content/images/tree_icon.png"),
                a_attr = { href = "http://www.bing.com" }
            });

            return Json(nodesList, JsonRequestBehavior.AllowGet);
        }
در ابتدا لیست گره‌ها تعریف می‌شود و سپس برای نمونه در این مثال، دو گره تعریف شده‌اند و در ادامه با فرمت JSON در اختیار افزونه قرار گرفته‌اند.
بنابراین ساختارهای خود ارجاع دهنده‌ را به خوبی می‌توان با این افزونه وفق داد.


فعال سازی اولیه سمت کلاینت افزونه jsTree

برای استفاد‌ه‌ی پویای از این افزونه در سمت کلاینت، فقط نیاز به یک DIV خالی است:
 <div id="jstree">
</div>
سپس jstree را بر روی این DIV فراخوانی می‌کنیم:
            $('#jstree').jstree({
                "core": {
                    "multiple": false,
                    "check_callback": true,
                    'data': {
                        'url': '@getTreeJsonUrl',
                        "type": "POST",
                        "dataType": "json",
                        "contentType": "application/json; charset=utf8",
                        'data': function (node) {
                            return { 'id': node.id };
                        }
                    },
                    'themes': {
                        'variant': 'small',
                        'stripes': true
                    }
                },
                "types": {
                    "default": {
                        "icon": '@Url.Content("~/Content/images/bookmark_book_open.png")'
                    },
                },
                "plugins": ["contextmenu", "dnd", "state", "types", "wholerow", "sort", "unique"],
                "contextmenu": {
                    "items": function (o, cb) {
                        var items = $.jstree.defaults.contextmenu.items();
                        items["create"].label = "ایجاد زیر شاخه";
                        items["rename"].label = "تغییر نام";
                        items["remove"].label = "حذف";
                        var cpp = items["ccp"];
                        cpp.label = "ویرایش";
                        var subMenu = cpp["submenu"];
                        subMenu["copy"].label = "کپی";
                        subMenu["paste"].label = "پیست";
                        subMenu["cut"].label = "برش";
                        return items;
                    }
                }
            });
توضیحات

- multiple : false به این معنا است که نمی‌خواهیم کاربر بتواند چندین گره را با نگه داشتن دکمه‌ی کنترل انتخاب کند.
- check_callback : true کدهای مرتبط با منوی کلیک سمت راست ماوس را فعال می‌کند.
- در قسمت data کار تبادل اطلاعات با سرور جهت دریافت فرمت JSON ایی که به آن اشاره شد، انجام می‌شود. متغیر getTreeJsonUrl یک چنین شکلی را می‌تواند داشته باشد:
 @{
ViewBag.Title = "Demo";
var getTreeJsonUrl =  Url.Action(actionName: "GetTreeJson", controllerName: "Home");
}
- در قسمت themes مشخص کرده‌ایم که از قالب small آن به همراه نمایش یک درمیان پس زمینه‌ی روشن و خاکستری استفاده شود. قالب large نیز دارد.
- در قسمت types که مرتبط است با افزونه‌ای به همین نام، آیکن پیش فرض یک نود جدید ایجاد شده را مشخص کرده‌ایم.
- گزینه‌ی plugins، لیست افزونه‌های اختیاری این افزونه را مشخص می‌کند. برای مثال contextmenu منوی کلیک سمت راست ماوس را فعال می‌کند، dnd همان کشیدن و رها کردن گره‌ها است در زیر شاخه‌های مختلف. افزونه‌ی state، انتخاب جاری کاربر را در سمت کلاینت ذخیره و در مراجعه‌ی بعدی او بازیابی می‌کند. با ذکر افزونه‌ی wholerow سبب می‌شویم که انتخاب یک گره، معادل انتخاب یک ردیف کامل از صفحه باشد. افزونه‌ی sort کار مرتب سازی خودکار اعضای یک زیر شاخه را انجام می‌دهد. افزونه‌ی unique سبب می‌شود تا در یک زیر شاخه نتوان دو عنوان یکسان را تعریف کرد.
- در قسمت contextmenu نحوه‌ی بومی سازی گزینه‌های منوی کلیک راست ماوس را مشاهده می‌کنید. در حالت پیش فرض، عناوینی مانند create، rename و امثال آن نمایش داده می‌شوند که به نحو فوق می‌توان آن‌را تغییر داد.

با همین حد تنظیم، این افزونه کار نمایش سلسله مراتبی اطلاعات JSON ایی دریافت شده از سرور را انجام می‌دهد.


ذخیره سازی گره‌های جدید و تغییرات سلسله مراتب پویای تعریف شده در سمت سرور

همانطور که عنوان شد، اگر افزونه‌ی اختیاری contextmenu را فعال کنیم، امکان افزودن، ویرایش و حذف گره‌ها و زیر شاخه‌ها را خواهیم یافت. برای انتقال این تغییرات به سمت سرور، باید به نحو ذیل عمل کرد:
            $('#jstree').jstree({
              // تمام تنظیمات مانند قبل
            }).on('delete_node.jstree', function (e, data) {
                })
                .on('create_node.jstree', function (e, data) {
                })
                .on('rename_node.jstree', function (e, data) {
                })
                .on('move_node.jstree', function (e, data) {
                })
                .on('copy_node.jstree', function (e, data) {
                })
                .on('changed.jstree', function (e, data) {
                })
                .on('dblclick.jstree', function (e) {
                })
                .on('select_node.jstree', function (e, data) {
                });
در اینجا نحوه‌ی تحت کنترل قرار دادن رخ‌دادهای مختلف این افزونه را مشاهده می‌کنید. برای مثال در callback مرتبط با delete_node کار حذف یک گره اطلاع رسانی می‌شود. create_node مربوط است به ایجاد یک گره یا زیر شاخه‌ی جدید. rename_node پس از تغییر نام یک گره فراخوانی خواهد شد. move_node مربوط است به کشیدن و رها کردن یک گره در یک زیر شاخه‌ی دیگر. copy_node برای copy/paste یک گره تعریف شده‌است. Changed یک callback عمومی است. dblclick برای عکس العمل نشان دادن به رخ‌داد دوبار کلیک کردن بر روی یک گره می‌تواند بکار گرفته شود. select_node با انتخاب یک گره فعال می‌شود.
در تمام این حالات، جایی که data در اختیار ما است، می‌توان یک چنین ساختار جاوا اسکریپتی را برای ارسال به سرور طراحی کرد:
        function postJsTreeOperation(operation, data, onDone, onFail) {
            $.post('@doJsTreeOperationUrl',
                {
                    'operation': operation,
                    'id': data.node.id,
                    'parentId': data.node.parent,
                    'position': data.position,
                    'text': data.node.text,
                    'originalId': data.original ? data.original.id : data.node.original.id,
                    'href': data.node.a_attr.href
                })
                .done(function (result) {
                    onDone(result);
                })
                .fail(function (result) {
                    alert('failed.....');
                    onFail(result);
                });
        }
به این ترتیب در سمت سرور می‌توان id یک گره، متن تغییر یافته آن، والد گره و بسیاری از مشخصات دیگر را دریافت و ثبت کرد. پس از تعریف متد postJsTreeOperation فوق، آن‌را باید به callbackهایی که پیشتر معرفی شدند، اضافه کرد؛ برای مثال:
                .on('create_node.jstree', function (e, data) {
                    postJsTreeOperation('CreateNode', data,
                        function (result) {
                            data.instance.set_id(data.node, result.id);
                        },
                        function (result) {
                            data.instance.refresh();
                        });
                })
در اینجا متد postJsTreeOperation، یک Operation خاص را مانند CreateNode (تعریف شده در enum ایی به نام JsTreeOperation در سمت سرور) به همراه data، به سرور post می‌کند.
و معادل سمت سرور دریافت کننده‌ی این اطلاعات، اکشن متد ذیل می‌تواند باشد:
        [HttpPost]
        public ActionResult DoJsTreeOperation(JsTreeOperationData data)
        {
            switch (data.Operation)
            {
                case JsTreeOperation.CopyNode:
                case JsTreeOperation.CreateNode:
                    //todo: save data
                    var rnd = new Random(); // آی دی رکورد پس از ثبت در بانک اطلاعاتی دریافت و بازگشت داده شود
                    return Json(new { id = rnd.Next() }, JsonRequestBehavior.AllowGet);

                case JsTreeOperation.DeleteNode:
                    //todo: save data
                    return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet);

                case JsTreeOperation.MoveNode:
                    //todo: save data
                    return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet);

                case JsTreeOperation.RenameNode:
                    //todo: save data
                    return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet);

                default:
                    throw new InvalidOperationException(string.Format("{0} is not supported.", data.Operation));
            }
        }
که در آن ساختار JsTreeOperationData به نحو ذیل تعریف شده‌است:
namespace MvcJSTree.Models
{
    public enum JsTreeOperation
    {
        DeleteNode,
        CreateNode,
        RenameNode,
        MoveNode,
        CopyNode
    }

    public class JsTreeOperationData
    {
        public JsTreeOperation Operation { set; get; }
        public string Id { set; get; }
        public string ParentId { set; get; }
        public string OriginalId { set; get; }
        public string Text { set; get; }
        public string Position { set; get; }
        public string Href { set; get; }
    }
}
این ساختار دقیقا با اعضای شیء جاوا اسکریپتی که متد postJsTreeOperation به سمت سرور ارسال می‌کند، تطابق دارد.
در اینجا Href را نیز مشاهده می‌کنید. همانطور که عنوان شد، اعضای JsTreeNodeAAttributes اختیاری هستند. بنابراین اگر این اعضاء را تغییر دادید، باید خواص JsTreeOperationData و همچنین اعضای شیء تعریف شده در postJsTreeOperation را نیز تغییر دهید تا با هم تطابق پیدا کنند.


چند نکته‌ی تکمیلی

اگر می‌خواهید که با دوبار کلیک بر روی یک گره، کاربر به href آن هدایت شود، می‌توان از کد ذیل استفاده کرد:
               var selectedData;
               // ...
                .on('dblclick.jstree', function (e) {
                    var href = selectedData.node.a_attr.href;
                    alert('selected node: ' + selectedData.node.text + ', href:' + href);

                    // auto redirect
                    if (href) {
                        window.location = href;
                    }

                    // activate edit mode
                    //var inst = $.jstree.reference(selectedData.node);
                    //inst.edit(selectedData.node);
                })
                .on('select_node.jstree', function (e, data) {
                    //alert('selected node: ' + data.node.text);
                    selectedData = data;
                });
در callback مرتبط با select_node می‌توان به گره انتخابی درستی یافت. سپس می‌توان این گره را در callback متناظر با dblclick برای یافتن href و مقدار دهی window.location که معادل redirect سمت کاربر است، بکار برد.
حتی اگر خواستید که با دوبار کلیک بر روی یک گره، گزینه‌ی ویرایش آن فعال شود، کدهای آن را به صورت کامنت مشاهده می‌کنید.


مثال کامل این بحث را از اینجا می‌توانید دریافت کنید:
MvcJSTree.zip
مطالب
مدیریت همزمانی و طول عمر توالی‌های Reactive extensions در یک برنامه‌ی دسکتاپ
پس از معرفی و مشاهده‌ی نحوه‌ی ایجاد توالی‌ها در Rx، بهتر است با نمونه‌ای از نحوه‌ی استفاده از آن در یک برنامه‌ی WPF آشنا شویم.
بنابراین ابتدا دو بسته‌ی Rx-Main و Rx-WPF را توسط نیوگت، به یک برنامه‌ی جدید WPF اضافه کنید:
 PM> Install-Package Rx-Main
PM> Install-Package Rx-WPF
فرض کنید قصد داریم محتوای یک فایل حجیم را به نحو ذیل خوانده و توسط Rx نمایش دهیم.
        private static IEnumerable<string> readFile(string filename)
        {
            using (TextReader reader = File.OpenText(filename))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    Thread.Sleep(100);
                    yield return line;
                }
            }
        }
در اینجا برای ایجاد یک توالی IEnumerable ، از yield return استفاده شده‌است. همچنین Thread.Sleep آن جهت بررسی قفل شدن رابط کاربری در حین خواندن فایل به عمد قرار گرفته است.
UI برنامه نیز به نحو ذیل است:
<Window x:Class="WpfApplicationRxTests.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition  Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Name="btnGenerateSequence" Click="btnGenerateSequence_Click">Generate sequence</Button>
        <ListBox Grid.Row="1" Name="lstNumbers"  />
        <Button Grid.Row="2" IsEnabled="False" Name="btnStop" Click="btnStop_Click">Stop</Button>
    </Grid>
</Window>
با این کدها
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;

namespace WpfApplicationRxTests
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private static IEnumerable<string> readFile(string filename)
        {
            using (TextReader reader = File.OpenText(filename))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    Thread.Sleep(100);
                    yield return line;
                }
            }
        }

        private IDisposable _subscribe;
        private void btnGenerateSequence_Click(object sender, RoutedEventArgs e)
        {
            btnGenerateSequence.IsEnabled = false;
            btnStop.IsEnabled = true;

            var items = new ObservableCollection<string>();
            lstNumbers.ItemsSource = items;
            _subscribe = readFile("test.txt").ToObservable()
                               .SubscribeOn(ThreadPoolScheduler.Instance)
                               .ObserveOn(DispatcherScheduler.Current)
                               .Finally(finallyAction: () =>
                               {
                                   btnGenerateSequence.IsEnabled = true;
                                   btnStop.IsEnabled = false;
                               })
                               .Subscribe(onNext: line =>
                               {
                                   items.Add(line);
                               },
                               onError: ex => { },
                               onCompleted: () =>
                               {
                                   //lstNumbers.ItemsSource = items;
                               });
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            _subscribe.Dispose();
        }
    }
}

توضیحات

حاصل متد readFile را که یک توالی معمولی IEnumerable را ایجاد می‌کند، توسط فراخوانی متد ToObservable، تبدیل به یک خروجی IObservable کرده‌ایم تا بتوانیم هربار که سطری از فایل مدنظر خوانده می‌شود، نسبت به آن واکنش نشان دهیم.
متد SubscribeOn مشخص می‌کند که این توالی Observable باید بر روی چه تردی اجرا شود. در اینجا از ThreadPoolScheduler.Instance استفاده شده‌است تا در حین خواندن فایل، رابط کاربری در حالت هنگ به نظر نرسد و ترد جاری (ترد اصلی برنامه) به صورت خودکار آزاد گردد.
از متد ObserveOn با پارامتر DispatcherScheduler.Current استفاده کرده‌ایم، تا نتیجه‌ی واکنش‌های به خوانده شدن سطرهای یک فایل مفروض، در ترد اصلی برنامه صورت گیرد. در غیر اینصورت امکان کار کردن با عناصر رابط کاربری در یک ترد دیگر وجود نخواهد داشت و برنامه کرش می‌کند.
در قسمت‌های قبل، صرفا متد Subscribe را مشاهده کرده بودید. در اینجا از متد Finally نیز استفاده شده‌است. علت اینجا است که اگر در حین خواندن فایل خطایی رخ دهد، قسمت onError متد Subscribe اجرا شده و دیگر به پارامتر onCompleted آن نخواهیم رسید. اما متد Finally آن همیشه در پایان عملیات اجرا می‌شود.
خروجی حاصل از متد Subscribe، از نوع IDisposable است. Rx به صورت خودکار پس از پردازش آخرین عنصر توالی، این شیء را Dispose می‌کند. اینجا است که callback متد Finally یاد شده فراخوانی خواهد شد. اما اگر در حین خواندن یک فایل طولانی، کاربر علاقمند باشد تا عملیات را متوقف کند، تنها کافی است که به صورت صریح، این شیء را Dispose نماید. به همین جهت است که مشاهده می‌کنید، این خروجی به صورت یک فیلد تعریف شده‌است تا در متد Stop بتوانیم آن‌را در صورت نیاز Dispose کنیم.


مثال فوق را از اینجا نیز می‌توانید دریافت کنید:
WpfApplicationRxTests.zip
  
مطالب
بررسی Bad code smell ها: زنجیره پیام یا Message chain
این کد بد بو در دسته «جلوگیری کنندگان از تغییر» قرار می‌گیرد. معمولا زمانیکه فراخوانی‌هایی مانند تکه کد زیر را در بخشی از کد مشاهده کردید، با چنین کد بد بویی مواجه هستید.  
MethodA().MethodB().MethodC();
فراخوانی هر یک از این متدها در خطی مجزا از کد نیز تشکیل دهنده‌ی این الگوی بد است. استفاده کننده‌ی از این زنجیره پیام، برای استفاده‌ی درست از آن، باید در جریان هریک از حلقه‌های زنجیره و ترتیب فراخوانی آنها باشد. در صورتیکه هر یک از حلقه‌های زنجیره تغییری داشتند، استفاده کننده  نیز باید تغییر کنند. 
به طور مثال:
public class RepresentativeEmployeeQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
} 
public class RepresentativeQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
} 
public class CustomerQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
}
public static class Programm 
{ 
    static void Main(string[] args) 
    { 
        var customer = new CustomerQuery().GetById(1); 
        var representativeId = customer.RepresentativeId; 
        var representative = new RepresentativeQuery().GetById(representativeId); 
        var managerId = representative.ManagerId; 
        var manager  = new RepresentativeEmployeeQuery().GetById(managerId); 
        var managerName = manager.FullName; 
    } 
}
  • کلاس CustomerQuery پرس و جوهای مربوط به مشتری را مدیریت می‌کند.
  • کلاس RepresentativeQuery پرس و جوهای مربوط به نمایندگی را مدیریت می‌کند .
  • کلاس RepresentativeEmployeeQuery پرس و جوهای مربوط به کارمندان نمایندگی را مدیریت می‌کند.
در مثال ذکر شده می‌خواهیم نام مدیریت نمایندگی ای را که یک مشتری از آن خرید کرده است، بدانیم. صرفا جهت نمایش مثال، این کار را در متد main انجام داده‌ایم.  
مشاهده می‌کنید که زنجیره‌ای از پیام‌ها از CustomerQuery تا پایین‌ترین قسمت یعنی RepresentativeEmployeeQuery ارسال شده است. هر یک از مراحل زنجیره بخشی از منطق دریافت نام را مدیریت می‌کنند. مانند دریافت مشتری، دریافت نمایندگی آن و دریافت مدیریت نمایندگی. 

مشکلات این کد بد بو 

مشکلاتی که با وجود چنین طراحی ای در کد بوجود می‌آیند می‌توانند گاهی اوقات پیچیده باشند. چند مورد از مشکلاتی که این نوع کد بوجود می‌آورد به صورت زیر هستند:
  • افزایش کدهای تکراری 
  • افزایش احتمال بروز اشکال در زنجیره فراخوانی‌ها با تغییر هر مرحله از آن 
  • نیاز به دانش درباره مراحل داخلی زنجیره، توسط تمامی استفاده کنندگان از آن (به طور مثال هر استفاده کننده‌ای که نام مدیر نمایندگی یک مشتری را نیاز دارد) 
  • افزایش احتمال بروز اشکال در پیاده سازی هر یک از استفاده کنندگان  


روش‌ها اصلاح این کد بد بو 

روش‌های اصلاح این کد بد بو حول مخفی کردن زنجیره فراخوانی‌ها هستند. اما در مواردی مانند مثال ذکر شده در این مطلب امکان ادغام یک یا چند متد نیز وجود دارد. به شرطی که این کار ناقض اصل Single responsibility یا دیگر اصول شیء گرایی نباشد. دو نمونه از روش‌های اصلاح این کد بد بو به صورت زیر هستند: 
  • مخفی کردن زنجیره فراخوانی و مستقل سازی استفاده کننده از زنجیره فرخوانی (Hide delegate)  
  • ادغام بخشی از متدها در زنجیره فراخوانی و از بین بردن زنجیره فراخوانی 


چه کدهایی Message Chain نیستند؟ 

معمولا کدهایی که از الگوی domain specific language پیروی می‌کنند، ممکن است شباهت بسیاری به مثال مطرح شده داشته باشند. اما از نظر مفهومی با الگوی مطرح شده متفاوت هستند؛ به این صورت که در کد بد بوی زنجیره پیام‌ها، استفاده کننده از متد نیاز به دانستن تمامی حلقه‌ها و ترتیب آنها را دارد، ولی در domain specific language‌ها معمولا نحوه استفاده از متدها به صورت شبه زبانی گویا هدایت شده و معمولا ترتیب به صورت مخفی مدیریت می‌شود.
مطالب
واژه‌های کلیدی جدید and، or و not در C# 9.0
یکی از ویژگی‌های زبان VB، شباهت بیش از اندازه‌ی آن به زبان انگلیسی است. برای مثال در این زبان با استفاده از not و and:
If Not a And b Then
  ...
Else
  ...
EndIf
می‌توان if‌های خواناتری را نسبت به #C ایجاد کرد:
if(!(a) && b)
{
...
}
else
{
}
در ادامه خواهیم دید که چگونه C# 9.0، این آرزوی دیرین را برآورده می‌کند! البته مایکروسافت در جای دیگری هم عنوان کرده‌است که زبان VB را دیگر پیگیری نمی‌کند و تغییر خاصی را در آن شاهد نخواهید بود. شاید به همین دلیل و جذب برنامه نویس‌های VB به #C، یک چنین تغییراتی رخ داده‌اند!


معرفی واژه‌ی کلیدی جدید not در C# 9.0

در ابتدا اینترفیس نمونه‌ای را به همراه دو کلاس مشتق شده‌ی از آن درنظر بگیرید:
public interface ICommand
{
}

public class Command1 : ICommand
{
}

public class Command2 : ICommand
{
}
اکنون اگر وهله‌ای از Command1 را ایجاد کرده و بخواهیم بررسی کنیم که آیا از نوع کلاس Command2 هست یا خیر، با استفاده از pattern matching و واژه‌ی کلیدی if می‌توان به صورت زیر عمل کرد:
ICommand command = new Command1();
if (!(command is Command2))
{

}
در C# 9.0، برای خواناتر کردن یک چنین بررسی‌هایی، می‌توان از pattern matching بهبود یافته‌ی آن و واژه‌ی کلیدی جدید not نیز استفاده کرد:
if (command is not Command2)
{

}


معرفی واژه‌های کلیدی جدید and و or در C# 9.0

واژه‌های کلیدی جدید and و or نیز درک و نوشتن عبارات pattern matching را بسیار ساده می‌کنند. برای نمونه قطعه کد متداول زیر را درنظر بگیرید:
if ((command is ICommand) && !(command is Command2))
{

}
اکنون در C# 9.0 با استفاده از واژه‌های کلیدی جدید and، or و not، می‌توان قطعه کد فوق را بسیار ساده کرد:
if (command is ICommand and not Command2)
{

}
نه تنها این قطعه کد ساده‌تر شده‌است، بلکه خوانایی آن افزایش یافته‌است و مانند یک سطر نوشته شده‌ی به زبان انگلیسی به نظر می‌رسد. همچنین در این حالت نیازی هم به تکرار command، در هر بار مقایسه نیست.
و یا حتی در اینجا در صورت نیاز می‌توان از واژه‌ی کلیدی جدید or نیز استفاده کرد:
if (command is Command1 or Command2)
{

}


امکان اعمال واژه‌های کلیدی جدید and، or و not به سایر نوع‌ها نیز وجود دارند

تا اینجا مثال‌هایی را که بررسی کردیم، در مورد بررسی نوع اشیاء بود. اما می‌توان این واژه‌های کلیدی جدید در C# 9.0 را به هر نوع ممکنی نیز اعمال کرد. برای نمونه، مثال ساده‌ی زیر را که در مورد بررسی اعداد است، درنظر بگیرید:
var number = new Random().Next(1, 10);
if (number > 2 && number < 8)
{

}
اکنون در C# 9.0 و با استفاده از امکانات جدید pattern matching آن می‌توان شرط متداول فوق را به صورت زیر ساده کرد:
if (number is > 2 and < 8)
{

}
در اینجا تنها یکبار نیاز به ذکر number است و از واژه‌های کلیدی is و and استفاده شده‌است.

یک مثال دیگر: متد زیر را در نظربگیرید که با استفاده از && و || متداول #C نوشته شده‌است:
public static bool IsLetterOrSeparator(char c) =>
   (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '.' || c == ',';
روش ارائه‌ی C# 9.0 ای آن به صورت زیر است:
public static bool IsLetterOrSeparator(char c) =>
   c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';


امکان اعمال واژه‌های کلیدی جدید and، or و not به switchها نیز وجود دارد

برای نمونه قطعه کد if/else دار متداول زیر را درنظر بگیرید که قابلیت تبدیل به یک سوئیچ را نیز دارد:
 var number = new Random().Next(1, 10);
 if (number <= 0)
 {
     Console.WriteLine("Less than or equal to 0");
 }
 else if (number > 0 && number <= 10)
 {
     Console.WriteLine("More than 0 but less than or equal to 10");
 }
 else
 {
     Console.WriteLine("More than 10");
 }
اگر بخواهیم همین قطعه کد را به کمک واژه‌های کلیدی جدید C# 9.0 و pattern matching بهبود یافته‌ی آن تبدیل به یک سوئیچ کنیم، به قطعه کد زیر خواهیم رسید:
// C#9.0
 switch (number)
 {
     case <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case > 0 and <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }
تا پیش از C# 7.0، سوئیچ‌های #C امکان بررسی باز‌ه‌ای از مقادیر را نداشتند. از آن زمان با معرفی pattern matching، چنین محدودیتی برطرف شد و اکنون می‌توان syntax قدیمی آن‌را توسط C# 9.0، بسیار خلاصه‌تر کرد. در ذیل، معادل قطعه کد فوق را بر اساس امکانات C# 7.0 مشاهده می‌کنید که خوانایی کمتری را داشته و حجم کد نویسی بیشتری را دارد:
// C#7.0
 switch (number)
 {
     case int value when value <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case int value when value > 0 && value <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }

و یا حتی می‌توان سوئیچ C# 9.0 را توسط switch expression بهبود یافته‌ی C# 8.0 نیز به شکل زیر بازنویسی کرد:
 var message = number switch
 {
     <= 0 => "Less than or equal to 0",
     > 0 and <= 10 => "More than 0 but less than or equal to 10",
     _ => "More than 10"
 };


انواع pattern matching‌های اضافه شده‌ی به C# 9.0

در این مطلب سعی شد مفاهیم pattern matching اضافه شده‌ی به C# 9.0، ذیل عنوان واژه‌های کلیدی جدید آن بحث شوند؛ اما هر کدام دارای نام‌های خاصی هم هستند:
الف) relational patterns: امکان استفاده‌ی از <, >, <= and >= را در الگوها میسر می‌کنند. مانند نمونه‌های سوئیچی که نوشته شد.
ب) logical patterns: امکان استفاده‌ی از واژه‌های کلیدی and، or و not را در الگوها ممکن می‌کنند.
ج) not pattern: امکان استفاده‌ی از واژه‌ی کلیدی not را در عبارات if میسر می‌کند.
د) Simple type pattern: در مثال‌های زیر، پس از انطباق با یک الگو، کاری با متغیر یا شیء مرتبط نداریم. در نگارش‌های قبلی برای صرفنظر کردن از آن، ذکر _ ضروری بود؛ اما در C#9.0 می‌توان آن‌را نیز ذکر نکرد:
private static int GetDiscount(Product p) => p switch
        {
            Food => 0, // Food _ => 0 before C# 9
            Book b => 75, // Book b _ => 75 before C# 9
            _ => 25
        };
مطالب
دسترسی به Collectionها در یک ترد دیگر در WPF
اگر در WPF سعی کنیم آیتمی را به مجموعه اعضای یک Collection مانند یک List یا ObservableCollection از طریق تردی دیگر اضافه کنیم، با خطای ذیل متوقف خواهیم شد:
 This type of CollectionView does not support changes to its SourceCollection
from a thread different from the Dispatcher thread
راه حلی که برای آن تا دات نت 4 در اکثر سایت‌ها توصیه می‌شد به نحو ذیل است:
Adding to an ObservableCollection from a background thread


مشکل!
اگر همین برنامه را که برای دات نت 4 کامپایل شده‌است، بر روی سیستمی که دات نت 4.5 بر روی آن نصب است اجرا کنیم، برنامه با خطای ذیل متوقف می‌شود:
 System.InvalidOperationException: This exception was thrown because the generator for control
'System.Windows.Controls.ListView Items.Count:62' with name '(unnamed)' has received sequence of
CollectionChanged events that do not agree with the current state of the Items collection.
The following differences were detected:  Accumulated count 61 is different from actual count 62.


مشکل از کجاست؟
در دات نت 4 و نیم، دیگر نیازی به استفاده از کلاس MTObservableCollection یاد شده نیست و به صورت توکار امکان کار با Collectionها از طریق تردی دیگر میسر است. فقط برای فعال سازی آن باید نوشت:
 private static object _lock = new object();
//...
BindingOperations.EnableCollectionSynchronization(persons, _lock);
پس از اینکه برای نمونه، مجموعه‌ی فرضی persons وهله سازی شد، تنها کافی است متد جدید EnableCollectionSynchronization بر روی آن فراخوانی شود.


برای برنامه‌ی دات نت 4 ایی که قرار است در سیستم‌های مختلف اجرا شود چطور؟

در اینجا باید از Reflection کمک گرفت. اگر متد EnableCollectionSynchronization بر روی کلاس BindingOperations یافت شد، یعنی برنامه‌ی دات نت 4، در محیط جدید در حال اجرا است:
public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)
{
      MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization",
             new Type[] { typeof(IEnumerable), typeof(object) });
      if (method != null)
      {
         method.Invoke(null, new object[] { collection, lockObject });
      }
}
در این حالت فقط کافی است این متد جدید یافت شده را بر روی Collection مدنظر فراخوانی کنیم.
همچنین اگر بخواهیم کلاس MTObservableCollection معرفی شده را جهت سازگاری با دات نت 4 و نیم به روز کنیم، به کلاس ذیل خواهیم رسید. این کلاس با دات نت 4 و 4.5 سازگار است و جهت کار با ObservableCollectionها از طریق تردهای مختلف تهیه شده‌است:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Data;
using System.Windows.Threading;

namespace WpfAsyncCollection
{
    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        private static object _syncLock = new object();

        public AsyncObservableCollection()
        {
            enableCollectionSynchronization(this, _syncLock);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            using (BlockReentrancy())
            {
                var eh = CollectionChanged;
                if (eh == null) return;

                var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                                  let dpo = nh.Target as DispatcherObject
                                  where dpo != null
                                  select dpo.Dispatcher).FirstOrDefault();

                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                    dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                    foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                        nh.Invoke(this, e);
                }
            }
        }

        private static void enableCollectionSynchronization(IEnumerable collection, object lockObject)
        {
            var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", 
                                    new Type[] { typeof(IEnumerable), typeof(object) });
            if (method != null)
            {
                // It's .NET 4.5
                method.Invoke(null, new object[] { collection, lockObject });
            }
        }
    }
}
در این کلاس، در سازنده‌ی آن متد عمومی enableCollectionSynchronization فراخوانی می‌شود. اگر برنامه در محیط دات نت 4 فراخوانی شود، تاثیری نخواهد داشت چون method در حال بررسی نال است. در غیراینصورت، برنامه در حالت سازگار با دات نت 4.5 اجرا خواهد شد.
مطالب
ایجاد «خواص الحاقی»
حتما با متدهای الحاقی یا Extension methods آشنایی دارید؛ می‌توان به یک شیء، که حتی منبع آن در دسترس ما نیست، متدی را اضافه کرد. سؤال: در مورد خواص چطور؟ آیا می‌شود به وهله‌ای از یک شیء موجود از پیش طراحی شده، یک خاصیت جدید را اضافه کرد؟
احتمالا شاید عنوان کنید که با اشیاء dynamic می‌توان چنین کاری را انجام داد. اما سؤال در مورد اشیاء غیر dynamic است.
یا نمونه‌ی دیگر آن Attached Properties در برنامه‌های مبتنی بر Xaml هستند. می‌توان به یک شیء از پیش موجود Xaml، خاصیتی را افزود که البته پیاده سازی آن منحصر است به همان نوع برنامه‌ها.


راه حل پیشنهادی

یک Dictionary را ایجاد کنیم تا ارجاعی از اشیاء، به عنوان کلید، در آن ذخیره شده و سپس key/valueهایی به عنوان value هر شیء، در آن ذخیره شوند. این key/valueها همان خواص و مقادیر آن‌ها خواهند بود. هر چند این راه حل به خوبی کار می‌کند اما ... مشکل نشتی حافظه دارد.
شیء Dictionary یک ارجاع قوی را از اشیاء، درون خودش نگه داری می‌کند و تا زمانیکه در حافظه باقی است، سیستم GC مجوز رهاسازی منابع آن‌ها را نخواهد یافت؛ چون عموما این نوع Dictionaryها باید استاتیک تعریف شوند تا طول عمر آن‌ها با طول عمر برنامه یکی گردد. بنابراین اساسا اشیایی که به این نحو قرار است پردازش شوند، هیچگاه dispose نخواهند شد. راه حلی برای این مساله در دات نت 4 به صورت توکار به دات نت فریم ورک اضافه شده‌است؛ به نام ساختار داده‌ای ConditionalWeakTable.


معرفی ConditionalWeakTable

ConditionalWeakTable جزو ساختارهای داده‌ای کمتر شناخته شده‌ی دات نت است. این ساختار داده، اشاره‌گرهایی را به ارجاعات اشیاء، درون خود ذخیره می‌کند. بنابراین چون ارجاعاتی قوی را به اشیاء ایجاد نمی‌کند، مانع عملکرد GC نیز نشده و برنامه در دراز مدت دچار مشکل نشتی حافظه نخواهد شد. هدف اصلی آن ایجاد ارتباطی بین CLR و DLR است. توسط آن می‌توان به اشیاء دلخواه، خواصی را افزود. به علاوه طراحی آن به نحوی است که thread safe است و مباحث قفل گذاری بر روی اطلاعات، به صورت توکار در آن پیاده سازی شده‌است. کار DLR فراهم آوردن امکان پیاده سازی زبان‌های پویایی مانند Ruby و Python برفراز CLR است. در این نوع زبان‌ها می‌توان به وهله‌هایی از اشیاء موجود، خاصیت‌های جدیدی را متصل کرد.
به صورت خلاصه کار ConditionalWeakTable ایجاد نگاشتی است بین وهله‌هایی از اشیاء CLR (اشیایی غیرپویا) و خواصی که به آن‌ها می‌توان به صورت پویا انتساب داد. در کار GC اخلال ایجاد نمی‌کند و همچنین می‌توان به صورت همزمان از طریق تردهای مختلف، بدون مشکل با آن کار کرد.


پیاده سازی خواص الحاقی به کمک ConditionalWeakTable

در اینجا نحوه‌ی استفاده از ConditionalWeakTable را جهت اتصال خواصی جدید به وهله‌های موجود اشیاء مشاهده می‌کنید:
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace ConditionalWeakTableSamples
{
    public static class AttachedProperties
    {
        public static ConditionalWeakTable<object,
            Dictionary<string, object>> ObjectCache = new ConditionalWeakTable<object,
                Dictionary<string, object>>();

        public static void SetValue<T>(this T obj, string name, object value) where T : class
        {
            var properties = ObjectCache.GetOrCreateValue(obj);

            if (properties.ContainsKey(name))
                properties[name] = value;
            else
                properties.Add(name, value);
        }

        public static T GetValue<T>(this object obj, string name)
        {
            Dictionary<string, object> properties;
            if (ObjectCache.TryGetValue(obj, out properties) && properties.ContainsKey(name))
                return (T)properties[name];
            return default(T);
        }

        public static object GetValue(this object obj, string name)
        {
            return obj.GetValue<object>(name);
        }
    }
}
ObjectCache تعریف شده از نوع استاتیک است؛ بنابراین در طول عمر برنامه زنده نگه داشته خواهد شد، اما اشیایی که به آن منتسب می‌شوند، خیر. هرچند به ظاهر در متد GetOrCreateValue، یک وهله از شیءایی موجود را دریافت می‌کند، اما در پشت صحنه صرفا IntPtr یا اشاره‌گری به این شیء را ذخیره سازی خواهد کرد. به این ترتیب در کار GC اخلالی صورت نخواهد گرفت و شیء مورد نظر، تا پایان کار برنامه به اجبار زنده نگه داشته نخواهد شد.


کاربرد اول

اگر با ASP.NET کار کرده باشید حتما با IPrincipal آشنایی دارید. خواصی مانند Identity یک کاربر در آن ذخیره می‌شوند.
سؤال: چگونه می‌توان یک خاصیت جدید به نام مثلا Disclaimer را به وهله‌ای از این شیء افزود:
    public static class ISecurityPrincipalExtension
    {
        public static bool Disclaimer(this IPrincipal principal)
        {
            return principal.GetValue<bool>("Disclaimer");
        }

        public static void SetDisclaimer(this IPrincipal principal, bool value)
        {
            principal.SetValue("Disclaimer", value);
        }
    }
در اینجا مثالی را از کاربرد کلاس AttachedProperties فوق مشاهده می‌کنید. توسط متد SetDisclaimer یک خاصیت جدید به نام Disclaimer به وهله‌ای از شیءایی از نوع  IPrincipal  قابل اتصال است. سپس توسط متد  Disclaimer قابل دستیابی خواهد بود.

اگر صرفا قرار است یک خاصیت به شیءایی متصل شود، روش ذیل نیز قابل استفاده می‌باشد (بجای استفاده از دیکشنری از یک کلاس جهت تعریف خاصیت اضافی جدید استفاده شده‌است):
using System.Runtime.CompilerServices;

namespace ConditionalWeakTableSamples
{
    public static class PropertyExtensions
    {
        private class ExtraPropertyHolder
        {
            public bool IsDirty { get; set; }
        }

        private static readonly ConditionalWeakTable<object, ExtraPropertyHolder> _isDirtyTable
                = new ConditionalWeakTable<object, ExtraPropertyHolder>();

        public static bool IsDirty(this object @this)
        {
            return _isDirtyTable.GetOrCreateValue(@this).IsDirty;
        }

        public static void SetIsDirty(this object @this, bool isDirty)
        {
            _isDirtyTable.GetOrCreateValue(@this).IsDirty = isDirty;
        }
    }
}


کاربرد دوم

ایجاد Id منحصربفرد برای اشیاء برنامه.
فرض کنید در حال نوشتن یک Entity framework profiler هستید. طراحی فعلی سیستم Interception آن به نحو زیر است:
public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{
}
سؤال: اینجا رویداد بسته شدن یک اتصال را دریافت می‌کنیم؛ اما ... دقیقا کدام اتصال؟ رویداد Opened را هم داریم اما چگونه این اشیاء را به هم مرتبط کنیم؟ شیء DbConnection دارای Id نیست. متد GetHashCode هم الزامی ندارد که اصلا پیاده سازی شده باشد یا حتی یک Id منحصربفرد را تولید کند. این متد با تغییر مقادیر خواص یک شیء می‌تواند مقادیر متفاوتی را ارائه دهد. در اینجا می‌خواهیم به ازای ارجاعی از یک شیء، یک Id منحصربفرد داشته باشیم تا بتوانیم تشخیص دهیم که این اتصال بسته شده، دقیقا کدام اتصال باز شده‌است؟
راه حل: خوب ... یک خاصیت Id را به اشیاء موجود متصل کنید!
using System;
using System.Runtime.CompilerServices;

namespace ConditionalWeakTableSamples
{
    public static class UniqueIdExtensions
    {
        static readonly ConditionalWeakTable<object, string> _idTable = 
                                    new ConditionalWeakTable<object, string>();

        public static string GetUniqueId(this object obj)
        {
            return _idTable.GetValue(obj, o => Guid.NewGuid().ToString());
        }

        public static string GetUniqueId(this object obj, string key)
        {
            return _idTable.GetValue(obj, o => key);
        }
    }
}
در اینجا مثالی دیگر از پیاده سازی و استفاده از ConditionalWeakTable را ملاحظه می‌کنید. اگر در کش آن ارجاعی به شیء مورد نظر وجود داشته باشد، مقدار Guid آن بازگشت داده می‌شود؛ اگر خیر، یک Guid به ارجاعی از شیء، انتساب داده شده و سپس بازگشت داده می‌شود. به عبارتی به صورت پویا یک خاصیت UniqueId به وهله‌هایی از اشیاء اضافه می‌شوند. به این ترتیب به سادگی می‌توان آن‌ها را ردیابی کرد و تشخیص داد که اگر این Guid پیشتر جایی به اتصال باز شده‌ای منتسب شده‌است، در چه زمانی و در کجا بسته شده است یا اصلا ... خیر. جایی بسته نشده‌است.


برای مطالعه بیشتر
The Conditional Weak Table: Enabling Dynamic Object Properties
How to create mixin using C# 4.0
Disclaimer Page using MVC
Extension Properties Revised
Easy Modeling
Providing unique ID on managed object using ConditionalWeakTable
مطالب
C# 7.1 - async Main
پس از فعالسازی C# 7.1، اولین ویژگی جدید C# 7.1، متدهای Main برنامه‌های کنسول (و همچنین WPF و WinForms) هستند که اینبار قابلیت تعریف async را نیز دارند.


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

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

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


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

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


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

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


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

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

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


اما ... اگر دقت کنید، متد async Task Main در اینجا ایندکس نشده‌است که به نظر کمبود نگارش فعلی VS 2017 است.
نظرات مطالب
استفاده از Interop.word برای جایگزین کردن مقادیر در تمامی فایل (Footer - Header - ... )
سلام؛ من ازهمین روش شما استفاده کردم چند وقت پیش به وسیله  bookmark:
        private Document oDoc;
        public void createdoc1()
        {
            var realpath="~/template";
            var filePath = Path.Combine(HttpContext.Current.Server.MapPath("~/template"), Lcourseid.Text + ".doc");
            var oWordApplication = new Application();
            DirectoryInfo dir = new DirectoryInfo(Server.MapPath(realpath));
            foreach (FileInfo files in dir.GetFiles())
            {
                files.Delete();
            }
            // To invoke MyMethod with the default argument value, pass  
            // Missing.Value for the optional parameter.
            object missing = System.Reflection.Missing.Value;

            //object fileName = ConfigurationManager.AppSettings["DocxPath"];@"C:\DocXExample.docx";
            string fileName = @"D:\template1.dot";
            //string fileName1 = @"D:\sss.doc";
            object newTemplate = false;

 object docType = 0;
            object isVisible = true;

            //System.Reflection.Missing.Value is used here for telling that method to use default parameter values when method execution
            oDoc = oWordApplication.Documents.Open(fileName, newTemplate, docType, isVisible, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
                ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
           // usable in earlier versions of Microsoft Word v2003  v11
          //  if(Convert.ToInt16(oWordApplication.Version) >=11)
            {
             //Sets or returns a Boolean that represents whether a document is being viewed in reading layout view.
              oDoc.ActiveWindow.View.ReadingLayout = false;
           }

            //The active window is the window that currently has focus.If there are no windows open, an exception is thrown.
            //microsoft.office.tools.word.
            oDoc.Activate();

            if (oDoc.Bookmarks.Exists("Title"))
            {
                oDoc.Bookmarks["Title"].Range.Text = "Test Field Entry from webform";
                oDoc.Bookmarks["Address"].Range.Text = "Address Field Entry from webform";
            }

            oDoc.SaveAs(filePath, ref missing);
            oWordApplication.Documents.Close(ref missing, ref missing, ref missing);
            //oWordApplication.Quit(ref SaveChanges, ref missing, ref missing, ref missing);
            ProcessRequest(filePath, Lcourseid.Text);
ولی این روش مشکلاتی هم داره. اول اینکه باید روی سرور تنظیمات خاصی رو انجام بدی. البته از تنظیمات منظور تنظیمات دسترسی کاربران هست. ولی استفاده از داک ایکس بیشتر استقبال میشه چون دردسرش کمتره.
پرسش‌ها
چگونه میتوانم یک پلاک خودرو را در سی شارپ بخوانم

درود بر همه عزیزان؛ زبان سی شارپ. اندازه پروژه کوچک.

برای خواندن متن (پلاک ماشین) از داخل تصویری که با OpenCVsharp گرفتم. (کیفیت تصاویر خوبه)

راه حل های طی شده :

1 :

با IronOCR و Tesseract کار کردم جواب نداد یا شاید بشه گفت من بلد نیستم چجوری از اینا استفاده کنم.

void Test1()
{
    var Ocr = new IronTesseract();
    Ocr.Language = OcrLanguage.Persian;
    using (var Input = new OcrInput("car-plate-iran.jpg"))
    {
       var Result = Ocr.Read(Input);
       var AllText = Result.Text;
       Console.WriteLine(AllText);
    }
}

2 :

امکانات آنلاین مثل api.ocr.space رو نمیتونم استفاده کنم

راه های مختلفی رو تست کردم، چت جی پی تی هم یسری کد میده همشون بدون خطا هستن ولی هیچ کدوم خروجی نمیدن. شاید قبلش کانفیگی چیزی لازمه و من بلد نیستم یا اینکه کلا سورس در رابطه با OCR بسیار کم هست؟! چیزی نمیدونم. ممنون میشم راهنمایی کنید

مطالب
تهیه یک DynamicXml برای خواندن اطلاعات فایل Xml با استفاده از انقیاد پویا در سی‌شارپ

در فریمورک NET. ابزارهای مختلفی برای کار با داده‌های XML در نظر گرفته شده‌است که بعد از نسخه 3.5 آن، انتخاب اول LINQ to XML می باشد. در این مطلب قصد داریم API ای را برای خواندن اطلاعات فایل‌های XML با استفاده از LINQ to XML و انقیاد پویا در سی‌شارپ (Dynamic Binding) تهیه کنیم.


راه حل اول: استفاده از ExpandoObject

public static class ExpandoXml
{
    public static dynamic AsExpando(this XDocument document)
    {
        return CreateExpando(document.Root);
    }

    private static dynamic CreateExpando(XElement element)
    {
        var result = new ExpandoObject() as IDictionary<string, object>;
        if (element.Elements().Any(e => e.HasElements))
        {
            var list = new List<ExpandoObject>();
            result.Add(element.Name.ToString(), list);
            foreach (var childElement in element.Elements())
            {
                list.Add(CreateExpando(childElement));
            }
        }
        else
        {
            foreach (var leafElement in element.Elements())
            {
                result.Add(leafElement.Name.ToString(), leafElement.Value);
            }
        }
        return result;
    }
}

در تکه کد بالا از طریق متد CreateExpando به صورت بازگشتی ابتدا بررسی می‌شود که آیا عنصر جاری دارای عناصری می‌باشد و همچنین آیا آنها دارای فرزند می‌باشند یا خیر؛ در صورت برقراری شرط، نتیجه‌ی اجرای متد CreateExpando بر روی تک تک عناصر فرزند را درون لیستی از ExpandoObject قرار داده و سپس آن لیست نیز به عنوان Value عنصر جاری در نظر گرفته می‌شود. در صورت عدم برقراری شرط مذکور، مقادیر مربوط به عناصر فرزند را در قالب یک ExpandoObject به عنوان خروجی بازگشت خواهد داد.


راه حل دوم: استفاده از DynamicObject

برای این منظور کلاس زیر را در نظر بگیرید:
    public class DynamicXml : DynamicObject, IEnumerable
    {
        private readonly dynamic _xml;
        public DynamicXml(string fileName)
        {
            _xml = XDocument.Load(fileName);
        }

        public DynamicXml(dynamic xml)
        {
            _xml = xml;
        }

        public IEnumerator GetEnumerator()
        {
            foreach (var item in _xml.Elements())
            {
                yield return new DynamicXml(item);
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var xml = _xml.Element(binder.Name);
            if (xml != null)
            {
                result = new DynamicXml(xml);
                return true;
            }

            var attribute = _xml.Attribute(binder.Name);
            if (attribute != null)
            {
                result = new DynamicXml(attribute);
                return true;
            }

            result = null;
            return false;
        }

        public static implicit operator string(DynamicXml xml)
        {
            return xml._xml.Value;
        }
    }

کلاس DynamicXml از طریق سازنده اول، نام فایل را دریافت کرده و از طریق LINQ to XML با استفاده از متد Load کلاس XDocument، فایل مورد نظر بارگذاری شده و درون فیلدی به نام xml_ از نوع dynamic نگه داشته می‌شود. کار بعدی، بازنویسی متد TryGetMember می‌باشد. در بدنه بازنویسی شده این متد ابتدا بررسی می‌شود که آیا با نام خصوصت درخواست شده عنصری در داده XML وجود دارد یا خیر؛ در صورت موجود بودن، پارامتر result با یک  وهله جدید از DynamicXml مقدار دهی می‌شود که عنصر یافت شده از طریق سازنده دوم، به عنوان داده xml برای مقدار دهی فیلد xml_ به عنوان آرگومان ارسال می‌شود. در صورت عدم وجود عنصر مذکور، بدنبال خصوصیتی با آن نام بوده و در صورت یافت شدن، باز به عنوان یک وهله DynamicXml برای مقدار دهی result استفاده می‌شود.

در ادامه برای نسبت دادن یک وهله از DynamicXml به یک متغیر string و دستیابی به مقدار یک عنصر که از طریق خصوصیت، درخواست می‌شود نیاز است تا اپراتور ضمنی string را نیز برای کلاس بالا نظر بگیریم. همچنین برای ایجاد امکان پیمایش برروی عناصر فرزند از طریق foreach، لازم است واسط IEnumerable را نیز پیاده سازی کرده باشیم.


طریقه استفاده

    class Program
    {
        static void Main(string[] args)
        {
            var doc1 = XDocument.Load("Employees.xml");
            
            foreach (var element in doc1.Element("Employees").Elements("Employee"))
            {
                Console.WriteLine(element.Element("FirstName").Value);
            }

            dynamic doc2 = XDocument.Load("Employees.xml").AsExpando();
            foreach (var employee in doc2.Employees)
            {
                Console.WriteLine(employee.FirstName);
            }

            dynamic doc3 = new DynamicXml("Employees.xml");
            foreach (var employee in doc3.Employees)
            {
                Console.WriteLine(employee.FirstName);
                Console.WriteLine(employee.Id);
            }
        }
    }
و فایل Employees.xml
<?xml version="1.0" encoding="utf-8" ?>
<Employees>
    <Employee Id="1">
        <FirstName>
            Employee1
        </FirstName>
    </Employee>
     <Employee Id="2">
        <FirstName>
            Employee2
        </FirstName>
    </Employee>
     <Employee Id="3">
        <FirstName>
            Employee3
        </FirstName>
    </Employee>
     <Employee Id="4">
        <FirstName>
            Employee4
        </FirstName>
    </Employee>
</Employees>