۱۳۹۰/۰۸/۰۷

محاسبه مجدد ميزان مصرف حافظه‌ي برنامه‌هاي دات نت


اگر به ميزان مصرف حافظه‌ اوليه‌ي برنامه‌هاي دات نت دقت كنيم، نسبت به مثلا يك برنامه‌ي MFC چند برابر به نظر مي‌رسند و ... اين علت دارد:
زمانيكه يك برنامه‌ي مبتني بر دات نت اجرا مي‌شود، ابتدا JIT compiler شروع به كار كرده و شروع به كامپايل برنامه مي‌كند. اين بارگزاري هم در همان پروسه‌ي اصلي برنامه انجام مي‌شود. به همين جهت ميزان مصرف حافظه‌ي برنامه‌هاي دات نت عموما بالا به نظر مي‌رسد.
اكنون سؤال اينجا است كه آيا مي توان اين حافظه‌اي را كه ديگر مورد استفاده نيست (و توسط JIT compiler اخذ شده) به سيستم بازگرداند و محاسبه‌ي مجددي را در اين مورد انجام داد. پاسخ به اين سؤال را در متد ReEvaluateWorkingSet زير مي‌توان مشاهده كرد:


using System;
using System.Diagnostics;

namespace Toolkit
{
    public static class Memory
    {
        public static void ReEvaluateWorkingSet()
        {
            try
            {
                Process loProcess = Process.GetCurrentProcess();
                //it doesn't matter what you set maxWorkingSet to
                //setting it to any value apparently causes the working set to be re-evaluated and excess discarded
                loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet + 1);
            }
            catch
            {
                //The above code requires Admin privileges. 
                //So it's important to trap exceptions in case you're running without admin rights. 
            }
        }
    }
}

در اين متد ابتدا پروسه جاري دريافت شده و سپس MaxWorkingSet به يك عدد دلخواه تنظيم مي‌شود. مهم نيست كه اين عدد چه چيزي باشد، زيرا اين تنظيم سبب مي‌شود كه در پشت صحنه به شكل حساب شده‌اي حافظه‌اي كه مورد استفاده نيست به سيستم بازگردانده شود و سپس عددي كه در task manager نمايش داده مي‌شود، مجددا محاسبه گردد. همچنين بايد دقت داشت كه اين كد تنها با دسترسي مديريتي قابل اجرا است و به همين دليل وجود اين try/catch ضروري است.

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌هاي WinForms :
فايل Program.cs را يافته و سپس در روال رويداد گردان Idle برنامه، متد ReEvaluateWorkingSet را فراخواني كنيد (مثلا هر زمان كه برنامه minimized شد اجرا مي‌شود):

//Program.cs
namespace MemUsage
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //...

            Application.Idle += applicationIdle;
        }

        static void applicationIdle(object sender, EventArgs e)
        {
            Memory.ReEvaluateWorkingSet();
        }
    }
}

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌هاي WPF :
فايل App.xaml.cs را يافته و سپس در روال رويدادگردان Deactivated برنامه، متد ReEvaluateWorkingSet را فراخواني كنيد:

//App.xaml.cs

public App()
{
    this.Deactivated += appDeactivated;
}

void appDeactivated(object sender, EventArgs e)
{
     Memory.ReEvaluateWorkingSet();
}

تاثير آن هم قابل ملاحظه است (حداقل از لحاظ رواني!). تست كنيد!

خلاصه اشتراک‌های روز جمعه 6 آبان 1390

۱۳۹۰/۰۸/۰۶

خلاصه اشتراک‌های روز پنج شنبه 5 آبان 1390


۱۳۹۰/۰۸/۰۵

آشنايي با Refactoring - قسمت 12



قبلا در مورد تبديل switch statement به الگوي استراتژي مطلبي را در اين سايت مطالعه كرده‌ايد (^) و بيشتر مربوط است به حالتي كه داخل هر يك از case هاي يك switch statement چندين و چند سطر كد و يا فراخواني يك تابع وجود دارد. حالت ساده‌تري هم براي refactoring يك عبارت switch وجود دارد و آن هم زماني است كه هر case، تنها از يك سطر تشكيل مي‌شود؛ مانند:

namespace Refactoring.Day12.RefactoringSwitchStatement.Before
{
    public class Translator
    {
        public string ToPersian(string englishWord)
        {
            switch (englishWord)
            {
                case "zero":
                    return "صفر";
                case "one":
                    return "يك";
                default:
                    return string.Empty;
            }
        }
    }
}

در اينجا مي‌توان از امكانات ساختار داده‌هاي توكار دات نت استفاده كرد و اين switch statement را به يك dictionary تبديل نمود:

using System.Collections.Generic;

namespace Refactoring.Day12.RefactoringSwitchStatement.After
{
    public class Translator
    {
        IDictionary<string, string> Words = new Dictionary<string, string>
        {
            { "zero", "صفر" },
            { "one", "يك" }
        };

        public string ToPersian(string englishWord)
        {
            string persianWord;
            if (Words.TryGetValue(englishWord, out persianWord))
            {
                return persianWord;
            }

            return string.Empty;
        }
    }
}

همانطور كه ملاحظه مي‌كنيد هر case به يك key و هر return به يك value در Dictionary تعريف شده، تبديل گشته‌اند. در اينجا هم بهتر است از متد TryGetValue جهت دريافت مقدار كليدها استفاده شود؛ زيرا در صورت فراخواني يك Dictionary با كليدي كه در آن موجود نباشد يك استثناء بروز خواهد كرد.
براي حذف اين متد TryGetValue، مي‌توان يك enum را بجاي كليدهاي تعريف شده، معرفي كرد. به صورت زير:

using System.Collections.Generic;

namespace Refactoring.Day12.RefactoringSwitchStatement.After
{
    public enum EnglishWord
    {
        Zero,
        One
    }

    public class Translator2
    {
        IDictionary<EnglishWord, string> Words = new Dictionary<EnglishWord, string>
        {
            { EnglishWord.Zero, "صفر" },
            { EnglishWord.One, "يك" }
        };

        public string ToPersian(EnglishWord englishWord)
        {
            return Words[englishWord];
        }
    }
}


به اين ترتيب از يك خروجي پر از if و else و switch به يك خروجي ساده و بدون وجود هيچ شرطي رسيده‌ايم.

خلاصه اشتراک‌های روز چهار شنبه 4 آبان 1390

۱۳۹۰/۰۸/۰۴

خلاصه اشتراک‌های روز سه شنبه 3 آبان 1390


۱۳۹۰/۰۸/۰۳

آشنايي با BOM !


سؤال: دو فايل زيرنويس فارسي داريم، هر دو هم با فرمت UTF-8 ذخيره شده‌اند. يكي در دستگاه DVD Player درست نمايش داده مي‌شود و ديگري خير. چرا؟!
هر دو فايل را اگر در يك اديتور متني باز كنيم تفاوتي قابل مشاهده نيست؛ اما در يك Hex Editor خير:



در سه بايت اول فايل با هم تفاوت دارند و اصطلاحا به اين سه بايت BOM يا Byte order mark گفته مي‌شود. توسط آن مي‌توان تشخيص داد كه فايل جاري اولا آيا با فرمت يونيكد ذخيره شده است يا خير ثانيا كدام حالت به كار گرفته شده است؛ آيا UTF-8 است يا UTF-16 يا ...؟
در حالت UTF-8 مقدار BOM مساوي با 0xEF,0xBB,0xBF بوده و البته ذكر آن اختياري است. به نظر اين دستگاه DVD Player ياد شده، به اين نكته حساس است.
در دات نت جهت اطمينان از نوشته شدن BOM در فايل توليدي، نياز است encoding نهايي صريحا ذكر گردد. براي مثال هرچند خروجي File.WriteAllText حتي بدون ذكر encoding آن، UTF-8 است، اما BOM را به همراه ندارد (^). براي رفع اين مساله بايد از روش زير استفاده كرد:

File.WriteAllText(path, data, Encoding.UTF8);  


خلاصه اشتراک‌های روز دو شنبه 2 آبان 1390


۱۳۹۰/۰۸/۰۱

آشنايي با Refactoring - قسمت 11


قسمت يازدهم آشنايي با Refactoring به توصيه‌هايي جهت بالا بردن خوانايي تعاريف مرتبط با اعمال شرطي مي‌پردازد.

الف) شرط‌هاي تركيبي را كپسوله كنيد

عموما حين تعريف شرط‌هاي تركيبي، هدف اصلي از تعريف آن‌ها پشت انبوهي از && و || گم مي‌شود و براي بيان مقصود، نياز به نوشتن كامنت خواهند داشت. مانند:

using System;

namespace Refactoring.Day11.EncapsulateConditional.Before
{
    public class Element
    {
        private string[] Data { get; set; }
        private string Name { get; set; }
        private int CreatedYear { get; set; }

        public string FindElement()
        {
            if (Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1)
                return "Element1";

            if (Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2)
                return "Element2";

            return string.Empty;
        }
    }
}

براي بالا بردن خوانايي اين نوع كدها كه برنامه نويس در همين لحظه‌ي تعريف آن‌ها دقيقا مي‌داند كه چه چيزي مقصود اوست، بهتر است هر يك از شرط‌ها را تبديل به يك خاصيت با معنا كرده و جايگزين كنيم. براي مثال مانند:

using System;

namespace Refactoring.Day11.EncapsulateConditional.After
{
    public class Element
    {
        private string[] Data { get; set; }
        private string Name { get; set; }
        private int CreatedYear { get; set; }

        public string FindElement()
        {
            if (hasOneYearOldElement)
                return "Element1";

            if (hasTwoYearsOldElement)
                return "Element2";

            return string.Empty;
        }

        private bool hasTwoYearsOldElement
        {
            get { return Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2; }
        }

        private bool hasOneYearOldElement
        {
            get { return Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1; }
        }
    }
}


همانطور كه ملاحظه مي‌كنيد پس از اين جايگزيني، خوانايي متد FindElement بهبود يافته است و برنامه نويس اگر 6 ماه بعد به اين كدها مراجعه كند نخواهد گفت: «من اين كدها رو نوشتم؟!»؛ چه برسد به سايريني كه احتمالا قرار است با اين كدها كار كرده و يا آن‌ها را نگهداري كنند.


ب) از تعريف خواص Boolean با نام‌هاي منفي پرهيز كنيد

يكي از مواردي كه عموما علت اصلي بروز بسياري از خطاها در برنامه است، استفاده از نام‌هاي منفي جهت تعريف خواص است. براي مثال در كلاس مشتري زير ابتدا بايد فكر كنيم كه مشتري‌هاي علامتگذاري شده كدام‌ها هستند كه حالا علامتگذاري نشده‌ها به اين ترتيب تعريف شده‌اند.

namespace Refactoring.Day11.RemoveDoubleNegative.Before
{
    public class Customer
    {
        public decimal Balance { get; set; }

        public bool IsNotFlagged
        {
            get { return Balance > 30m; }
        }
    }
}

همچنين از تعريف اين نوع خواص در فايل‌هاي كانفيگ برنامه‌ها نيز جدا پرهيز كنيد؛ چون عموما كاربران برنامه‌ها با اين نوع نامگذاري‌هاي منفي، مشكل مفهومي دارند.
Refactoring قطعه كد فوق بسيار ساده است و تنها با معكوس كردن شرط و نحوه‌ي نامگذاري خاصيت IsNotFlagged پايان مي‌يابد:

namespace Refactoring.Day11.RemoveDoubleNegative.After
{
    public class Customer
    {
        public decimal Balance { get; set; }

        public bool IsFlagged
        {
            get { return Balance <= 30m; }
        }
    }
}

خلاصه اشتراک‌های روز شنبه 30 مهر 1390


۱۳۹۰/۰۷/۲۹

راهنماي انتخاب فناوري‌هاي مختلف


اگر مطالب مرتبط با WinRT را دنبال كرده باشيد، شركت Telerik فلوچارتي را به عنوان راهنماي انتخاب فناوري‌هاي مختلف جهت توسعه برنامه‌هاي ويندوز با جهت گيري دات نت، منتشر كرده‌‌است كه برگردان فارسي آن به صورت زير است:


همين تصوير با فرمت‌هاي PDF و Visio

خلاصه اشتراک‌های روز پنج شنبه 28 مهر 1390


  • Roslyn CTP | Kirill Osenkov - MSFT | blogs.msdn.com
  • Windows Server 8 | Bill Laing, Manlio Vecchiet, Max Herrmann, Mike Neil | channel9.msdn.com

۱۳۹۰/۰۷/۲۸

آشنايي با Refactoring - قسمت 10


يكي ديگر از روش‌هايي كه جهت بهبود كيفيت كدها مورد استفاده قرار مي‌گيرد، «طراحي با قراردادها» است؛ به اين معنا كه «بهتر است» متدهاي تعريف شده پيش از استفاده از آرگومان‌هاي خود، آن‌ها را دقيقا بررسي كنند و به اين نوع پيش شرط‌ها، قرارداد هم گفته مي‌شود.
نمونه‌اي از آن‌را در قسمت 9 مشاهده كرديد كه در آن اگر آرگومان‌هاي متد AddRole، خالي يا نال باشند، يك استثناء صادر مي‌شود. اين نوع پيغام‌هاي واضح و دقيق در مورد عدم اعتبار ورودي‌هاي دريافتي، بهتر است از پيغام‌هاي كلي و نامفهوم null reference exception كه بدون بررسي stack trace و ساير ملاحظات، علت بروز آن‌ها مشخص نمي‌شوند.
در دات نت 4، جهت سهولت اين نوع بررسي‌ها، مفهوم Code Contracts ارائه شده است. (اين نام هم از اين جهت بكارگرفته شده كه Design by Contract نام تجاري شركت ثبت شده‌اي در آمريكا است!)


يك مثال:
متد زير را در نظر بگيريد. اگر divisor مساوي صفر باشد، استثناي كلي DivideByZeroException صادر مي‌شود:

namespace Refactoring.Day10.DesignByContract.Before
{
    public class MathMehods
    {
        public double Divide(int dividend, int divisor)
        {
            return dividend / divisor;
        }
    }
}

روش متداول «طراحي با قراردادها» جهت بهبود كيفيت كد فوق پيش از دات نت 4 به صورت زير است:

using System;

namespace Refactoring.Day10.DesignByContract.After
{
    public class MathMehods
    {
        public double Divide(int dividend, int divisor)
        {
            if (divisor == 0)
                throw new ArgumentException("divisor cannot be zero", "divisor");

            return dividend / divisor;
        }
    }
}

در اينجا پس از بررسي آرگومان divisor، قرارداد خود را به آن اعمال خواهيم كرد. همچنين در استثناي تعريف شده، پيغام واضح‌تري به همراه نام آرگومان مورد نظر، ذكر شده است كه از هر لحاظ نسبت به استثناي استاندارد و كلي DivideByZeroException مفهوم‌تر است.

در دات نت 4 ، به كمك امكانات مهياي در فضاي نام System.Diagnostics.Contracts، اين نوع بررسي‌ها نام و امكانات درخور خود را يافته‌اند:

using System.Diagnostics.Contracts;

namespace Refactoring.Day10.DesignByContract.After
{
    public class MathMehods
    {
        public double Divide(int dividend, int divisor)
        {
            Contract.Requires(divisor != 0, "divisor cannot be zero");

            return dividend / divisor;
        }
    }
}

البته اگر قطعه كد فوق را به همراه divisor=0 اجرا كنيد، هيچ پيغام خاصي را مشاهده نخواهيد كرد؛ از اين لحاظ كه نياز است تا فايل‌هاي مرتبط با آن‌را از اين آدرس دريافت و نصب كنيد. اين كتابخانه با VS2008 و VS2010 سازگار است. پس از آن، برگه‌ي Code contracts به عنوان يكي از برگه‌هاي خواص پروژه در دسترس خواهد بود و به كمك آن مي‌توان مشخص كرد كه برنامه حين رسيدن به اين نوع بررسي‌ها چه عكس العملي را بايد بروز دهد.

براي مطالعه بيشتر:

خلاصه اشتراک‌های روز چهار شنبه 27 مهر 1390


۱۳۹۰/۰۷/۲۵

آشنايي با Refactoring - قسمت 9


اين قسمت از آشنايي با Refactoring به كاهش cyclomatic complexity اختصاص دارد و خلاصه آن اين است: «استفاده از if هاي تو در تو بيش از سه سطح، مذموم است» به اين علت كه پيچيدگي كدهاي نوشته شده را بالا برده و نگهداري آن‌ها را مشكل مي‌كند. براي مثال به شبه كد زير دقت كنيد:

if
   if
     if
       if
         do something
       endif
     endif
   endif
 endif


كه حاصل آن شبيه به نوك يك پيكان (Arrow head) شده است. يك مثال بهتر:


namespace Refactoring.Day9.RemoveArrowhead.Before
{
    public class Role
    {
        public string RoleName { set; get; }
        public string UserName { set; get; }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;

namespace Refactoring.Day9.RemoveArrowhead.Before
{
    public class RoleRepository
    {
        private IList<Role> _rolesList = new List<Role>();

        public IEnumerable<Role> Roles { get { return _rolesList; } }

        public void AddRole(string username, string roleName)
        {
            if (!string.IsNullOrWhiteSpace(roleName))
            {
                if (!string.IsNullOrWhiteSpace(username))
                {
                    if (!IsInRole(username, roleName))
                    {
                        _rolesList.Add(new Role 
                        {
                            UserName=username, 
                            RoleName=roleName
                        }); 
                    }
                    else
                    {
                        throw new InvalidOperationException("User is already in this role.");
                    }
                }
                else
                {
                    throw new ArgumentNullException("username");
                }
            }
            else
            {
                throw new ArgumentNullException("roleName");
            }
        }

        public bool IsInRole(string username, string roleName)
        {
            return _rolesList.Any(x => x.RoleName == roleName && x.UserName == username);
        }
    }
}

متد AddRole فوق، نمونه‌ي بارز پيچيدگي بيش از حد حاصل از اعمال if هاي تو در تو است و ... بسيار متداول. براي حذف اين نوك پيكان حاصل از if هاي تو در تو، از بالاترين سطح شروع كرده و شرط‌ها را برعكس مي‌كنيم؛ با اين هدف كه هر چه سريعتر متد را ترك كرده و خاتمه دهيم:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Refactoring.Day9.RemoveArrowhead.After
{
    public class RoleRepository
    {
        private IList<Role> _rolesList = new List<Role>();

        public IEnumerable<Role> Roles { get { return _rolesList; } }

        public void AddRole(string username, string roleName)
        {
            if (string.IsNullOrWhiteSpace(roleName))
                throw new ArgumentNullException("roleName");

            if (string.IsNullOrWhiteSpace(username))
                throw new ArgumentNullException("username");

            if (IsInRole(username, roleName))
                throw new InvalidOperationException("User is already in this role.");

            _rolesList.Add(new Role
            {
                UserName = username,
                RoleName = roleName
            });
        }

        public bool IsInRole(string username, string roleName)
        {
            return _rolesList.Any(x => x.RoleName == roleName && x.UserName == username);
        }
    }
}

اكنون پس از اعمال اين Refactoring ، متد AddRole بسيار خواناتر شده و هدف اصلي آن كه اضافه كردن يك شيء به ليست نقش‌ها است، واضح‌تر به نظر مي‌رسد. به علاوه اينبار قسمت‌هاي مختلف متد AddRole، فقط يك كار را انجام مي‌دهند و وابستگي‌هاي آن‌ها به يكديگر نيز كاهش يافته است.


خلاصه اشتراک‌های روز يك شنبه 24 مهر 1390


  • درد دل | عارف حکیمی | www.parnianportal.com

۱۳۹۰/۰۷/۲۳

استفاده از گرافيك برداري در iTextSharp



در مورد «ترسيم اشكال گرافيكي با iTextSharp» مطلب مفصلي را در اينجا مي‌توانيد مطالعه كنيد؛ كه قصد تكرار مجدد آن‌را ندارم. فقط اين روش‌ها يك مشكل مهم دارند : «كار من ترسيم اين نوع اشكال گرافيكي نيست!». مثلا من الان نياز دارم در گزارشي، بجاي ستون Boolean آن در مواردي كه مقدار رديف true هست، مثلا يك «چك مارك» را بجاي true/false يا بله/خير نمايش دهم. مي‌شود اينكار را با يك تصوير معمولي هم انجام داد. فقط حجم فايل حاصل، بيش از اندازه بالا مي‌رود و همچنين نتيجه استفاده از يك bitmap، به زيبايي بكارگيري گرافيك برداري با قابليت تغيير ابعاد بدون نگراني در مورد از دست دادن كيفيت آن، نيست.

خوشبختانه هستند سايت‌هايي كه اين نوع تصاوير برداري را به رايگان ارائه دهند؛ براي مثال: سايت Openclipart، تعداد قابل توجهي فايل با فرمت SVG دارد. فايل‌هاي SVG را مستقيما نمي‌توان توسط iTextSharp استفاده كرد؛ اما يك سري برنامه‌ي كمكي براي تبديل فرمت SVG به مثلا XAML (قابل توجه برنامه نويس‌هاي WPF و Silverlight) يا WMF و غيره وجود دارد. براي نمونه iTextSharp امكان خواندن فايل‌هاي WMF را داشته (توسط همان متد معروف Image.GetInstance آن) و اينبار اين Image حاصل، يك تصوير برداري است و نه يك Bitmap.
در بين اين برنامه‌هاي تبديل كننده‌ فرمت‌هاي برداري، برنامه‌ي معروف و سورس باز Inkscape، در صدر محبوبيت قرار دارد. تنها كافي است فايل SVG خود را در آن گشوده و سپس به انواع و اقسام فرمت‌هاي ديگر تبديل (Save As) كنيد:



يكي از فرمت‌هاي جالب خروجي آن، Tex است (مربوط به يك برنامه اديتور، به نام LaTeX است). فرض كنيد يكي از اين «چك مارك»هاي سايت Openclipart را در برنامه Inkscape باز كرده‌ و سپس با فرمت Tex ذخيره كرده‌ايم. خروجي فايل متني آن مثلا به شكل زير خواهد بود:

%LaTeX with PSTricks extensions
%%Creator: 0.48.0
%%Please note this file requires PSTricks extensions
\psset{xunit=.5pt,yunit=.5pt,runit=.5pt}
\begin{pspicture}(190,190)
{
\newrgbcolor{curcolor}{0 0 0}
\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor]
{
\newpath
\moveto(52.73079005,101.89500456)
\curveto(31.29686559,101.89500456)(13.84575258,84.04652127)(13.8457479,62.12456369)
\curveto(13.8457479,40.20259605)(31.29686559,22.35412714)(52.73079005,22.35412235)
\curveto(74.16470983,22.35412235)(91.6158322,40.20259605)(91.61582751,62.12456369)
\curveto(91.61582751,71.60188248)(88.48023622,80.07729424)(83.15553076,87.02034164)
\lineto(79.49425309,82.58209245)
\curveto(84.13622847,76.73639073)(85.95313131,70.24630402)(85.95313131,62.12456369)
\curveto(85.95313131,43.33817595)(71.09893654,28.1547277)(52.73079005,28.1547277)
\curveto(34.36263419,28.15473249)(19.50844879,43.33817595)(19.50844879,62.12456369)
\curveto(19.50844879,80.91094185)(34.36264355,96.10336589)(52.73079005,96.10336589)
\curveto(58.55122776,96.10336589)(62.90459266,95.2476225)(67.65721002,92.5630926)
\lineto(71.13570481,97.23509821)
\curveto(65.57113223,100.3782653)(59.52269945,101.89500456)(52.73079005,101.89500456)
\closepath
}
}
{
\newrgbcolor{curcolor}{0 0 0}
\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor]
{
\newpath
\moveto(38.33889376,67.35513328)
\curveto(39.90689547,67.35509017)(41.09296342,66.03921993)(41.89711165,63.40748424)
\curveto(43.50531445,58.47289182)(44.65118131,56.00562195)(45.33470755,56.0056459)
\curveto(45.85735449,56.00562195)(46.40013944,56.41682961)(46.96305772,57.23928802)
\curveto(58.2608517,75.74384316)(68.7143666,90.71198997)(78.32362116,102.14379168)
\curveto(80.81631349,105.10443984)(84.77658911,106.58480942)(90.20445269,106.58489085)
\curveto(91.49097185,106.58480942)(92.35539361,106.46145048)(92.79773204,106.21480444)
\curveto(93.23991593,105.96799555)(93.4610547,105.65958382)(93.46113432,105.28956447)
\curveto(93.4610547,104.71379041)(92.7976618,103.58294901)(91.47094155,101.89705463)
\curveto(75.95141033,82.81670149)(61.55772504,62.66726353)(48.28984822,41.44869669)
\curveto(47.36506862,39.96831273)(45.47540199,39.22812555)(42.62081088,39.22813992)
\curveto(39.72597184,39.22812555)(38.0172148,39.35149407)(37.49457722,39.5982407)
\curveto(36.12755286,40.2150402)(34.51931728,43.36081778)(32.66987047,49.03557823)
\curveto(30.57914689,55.32711903)(29.53378743,59.27475848)(29.53381085,60.87852533)
\curveto(29.53378743,62.60558406)(30.94099884,64.27099685)(33.75542165,65.87476369)
\curveto(35.48425582,66.86164481)(37.01207517,67.35509017)(38.33889376,67.35513328)
}
}

\end{pspicture}


استفاده از اين خروجي در iTextSharp بسيار ساده است. براي مثال:

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

namespace HtmlToPdf
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var cb = pdfWriter.DirectContent;
                                
                cb.MoveTo(52.73079005f, 101.89500456f);
                cb.CurveTo(31.29686559f, 101.89500456f, 13.84575258f, 84.04652127f, 13.8457479f, 62.12456369f);
                cb.CurveTo(13.8457479f, 40.20259605f, 31.29686559f, 22.35412714f, 52.73079005f, 22.35412235f);
                cb.CurveTo(74.16470983f, 22.35412235f, 91.6158322f, 40.20259605f, 91.61582751f, 62.12456369f);
                cb.CurveTo(91.61582751f, 71.60188248f, 88.48023622f, 80.07729424f, 83.15553076f, 87.02034164f);
                cb.LineTo(79.49425309f, 82.58209245f);
                cb.CurveTo(84.13622847f, 76.73639073f, 85.95313131f, 70.24630402f, 85.95313131f, 62.12456369f);
                cb.CurveTo(85.95313131f, 43.33817595f, 71.09893654f, 28.1547277f, 52.73079005f, 28.1547277f);
                cb.CurveTo(34.36263419f, 28.15473249f, 19.50844879f, 43.33817595f, 19.50844879f, 62.12456369f);
                cb.CurveTo(19.50844879f, 80.91094185f, 34.36264355f, 96.10336589f, 52.73079005f, 96.10336589f);
                cb.CurveTo(58.55122776f, 96.10336589f, 62.90459266f, 95.2476225f, 67.65721002f, 92.5630926f);
                cb.LineTo(71.13570481f, 97.23509821f);
                cb.CurveTo(65.57113223f, 100.3782653f, 59.52269945f, 101.89500456f, 52.73079005f, 101.89500456f);
                                
                cb.MoveTo(38.33889376f, 67.35513328f);
                cb.CurveTo(39.90689547f, 67.35509017f, 41.09296342f, 66.03921993f, 41.89711165f, 63.40748424f);
                cb.CurveTo(43.50531445f, 58.47289182f, 44.65118131f, 56.00562195f, 45.33470755f, 56.0056459f);
                cb.CurveTo(45.85735449f, 56.00562195f, 46.40013944f, 56.41682961f, 46.96305772f, 57.23928802f);
                cb.CurveTo(58.2608517f, 75.74384316f, 68.7143666f, 90.71198997f, 78.32362116f, 102.14379168f);
                cb.CurveTo(80.81631349f, 105.10443984f, 84.77658911f, 106.58480942f, 90.20445269f, 106.58489085f);
                cb.CurveTo(91.49097185f, 106.58480942f, 92.35539361f, 106.46145048f, 92.79773204f, 106.21480444f);
                cb.CurveTo(93.23991593f, 105.96799555f, 93.4610547f, 105.65958382f, 93.46113432f, 105.28956447f);
                cb.CurveTo(93.4610547f, 104.71379041f, 92.7976618f, 103.58294901f, 91.47094155f, 101.89705463f);
                cb.CurveTo(75.95141033f, 82.81670149f, 61.55772504f, 62.66726353f, 48.28984822f, 41.44869669f);
                cb.CurveTo(47.36506862f, 39.96831273f, 45.47540199f, 39.22812555f, 42.62081088f, 39.22813992f);
                cb.CurveTo(39.72597184f, 39.22812555f, 38.0172148f, 39.35149407f, 37.49457722f, 39.5982407f);
                cb.CurveTo(36.12755286f, 40.2150402f, 34.51931728f, 43.36081778f, 32.66987047f, 49.03557823f);
                cb.CurveTo(30.57914689f, 55.32711903f, 29.53378743f, 59.27475848f, 29.53381085f, 60.87852533f);
                cb.CurveTo(29.53378743f, 62.60558406f, 30.94099884f, 64.27099685f, 33.75542165f, 65.87476369f);
                cb.CurveTo(35.48425582f, 66.86164481f, 37.01207517f, 67.35509017f, 38.33889376f, 67.35513328f);
                
                cb.SetRGBColorFill(0, 0, 0);
                cb.Fill();
            }

            Process.Start("Test.pdf");
        }
    }
}

در اينجا، pdfWriter.DirectContent يك Canvas را جهت ترسيمات گرافيكي در اختيار ما قرار مي‌دهد. سپس مابقي هم آن مشخص است و يك تناظر يك به يك را مي‌شود بين خروجي Tex و متدهاي فراخواني شده، مشاهده كرد. PDF خروجي هم به شكل زير است:



تا اينجا يك مرحله پيشرفت است. مشكل از اينجا شروع مي‌شود كه خوب! من كه يك «چك مارك» اين اندازه‌اي لازم ندارم! آن هم قرار گرفته در پايين صفحه. يك راه حل اين مشكل استفاده از متد Transform شيء cb فوق است. اين متد يك System.Drawing.Drawing2D.Matrix را دريافت مي‌كند و سپس مي‌شود توسط آن، اعمال تغيير اندازه (Scale)، تغيير مكان (Translate) و غيره را اعمال كرد. راه ديگر تعريف يك Template از دستورات فوق است. سپس متد Image.GetInstance كتابخانه iTextSharp ورودي از نوع Template را هم قبول مي‌كند. خروجي حاصل يك تصوير برداري خواهد بود كه اكنون با اكثر اشياء iTextSharp سازگار است. براي مثال متد سازنده PdfPCell، آرگومان از نوع Image را هم قبول مي‌كند. به علاوه شيء Image در اينجا متدهاي تغيير اندازه و امثال آن‌را نيز به همراه دارد:

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

namespace HtmlToPdf
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var cb = pdfWriter.DirectContent;
                var template = createCheckMark(cb);

                var image = Image.GetInstance(template);
                image.ScaleAbsolute(40, 40);

                var table = new PdfPTable(3);
                var cell = new PdfPCell(image) 
                {
                    HorizontalAlignment = Element.ALIGN_CENTER
                };

                for (int i = 0; i < 9; i++)
                    table.AddCell(cell);

                pdfDoc.Add(table);
            }

            Process.Start("Test.pdf");
        }

        private static PdfTemplate createCheckMark(PdfContentByte cb)
        {
            var template = cb.CreateTemplate(140, 140);

            template.MoveTo(52.73079005f, 101.89500456f);
            template.CurveTo(31.29686559f, 101.89500456f, 13.84575258f, 84.04652127f, 13.8457479f, 62.12456369f);
            template.CurveTo(13.8457479f, 40.20259605f, 31.29686559f, 22.35412714f, 52.73079005f, 22.35412235f);
            template.CurveTo(74.16470983f, 22.35412235f, 91.6158322f, 40.20259605f, 91.61582751f, 62.12456369f);
            template.CurveTo(91.61582751f, 71.60188248f, 88.48023622f, 80.07729424f, 83.15553076f, 87.02034164f);
            template.LineTo(79.49425309f, 82.58209245f);
            template.CurveTo(84.13622847f, 76.73639073f, 85.95313131f, 70.24630402f, 85.95313131f, 62.12456369f);
            template.CurveTo(85.95313131f, 43.33817595f, 71.09893654f, 28.1547277f, 52.73079005f, 28.1547277f);
            template.CurveTo(34.36263419f, 28.15473249f, 19.50844879f, 43.33817595f, 19.50844879f, 62.12456369f);
            template.CurveTo(19.50844879f, 80.91094185f, 34.36264355f, 96.10336589f, 52.73079005f, 96.10336589f);
            template.CurveTo(58.55122776f, 96.10336589f, 62.90459266f, 95.2476225f, 67.65721002f, 92.5630926f);
            template.LineTo(71.13570481f, 97.23509821f);
            template.CurveTo(65.57113223f, 100.3782653f, 59.52269945f, 101.89500456f, 52.73079005f, 101.89500456f);

            template.MoveTo(38.33889376f, 67.35513328f);
            template.CurveTo(39.90689547f, 67.35509017f, 41.09296342f, 66.03921993f, 41.89711165f, 63.40748424f);
            template.CurveTo(43.50531445f, 58.47289182f, 44.65118131f, 56.00562195f, 45.33470755f, 56.0056459f);
            template.CurveTo(45.85735449f, 56.00562195f, 46.40013944f, 56.41682961f, 46.96305772f, 57.23928802f);
            template.CurveTo(58.2608517f, 75.74384316f, 68.7143666f, 90.71198997f, 78.32362116f, 102.14379168f);
            template.CurveTo(80.81631349f, 105.10443984f, 84.77658911f, 106.58480942f, 90.20445269f, 106.58489085f);
            template.CurveTo(91.49097185f, 106.58480942f, 92.35539361f, 106.46145048f, 92.79773204f, 106.21480444f);
            template.CurveTo(93.23991593f, 105.96799555f, 93.4610547f, 105.65958382f, 93.46113432f, 105.28956447f);
            template.CurveTo(93.4610547f, 104.71379041f, 92.7976618f, 103.58294901f, 91.47094155f, 101.89705463f);
            template.CurveTo(75.95141033f, 82.81670149f, 61.55772504f, 62.66726353f, 48.28984822f, 41.44869669f);
            template.CurveTo(47.36506862f, 39.96831273f, 45.47540199f, 39.22812555f, 42.62081088f, 39.22813992f);
            template.CurveTo(39.72597184f, 39.22812555f, 38.0172148f, 39.35149407f, 37.49457722f, 39.5982407f);
            template.CurveTo(36.12755286f, 40.2150402f, 34.51931728f, 43.36081778f, 32.66987047f, 49.03557823f);
            template.CurveTo(30.57914689f, 55.32711903f, 29.53378743f, 59.27475848f, 29.53381085f, 60.87852533f);
            template.CurveTo(29.53378743f, 62.60558406f, 30.94099884f, 64.27099685f, 33.75542165f, 65.87476369f);
            template.CurveTo(35.48425582f, 66.86164481f, 37.01207517f, 67.35509017f, 38.33889376f, 67.35513328f);

            template.SetRGBColorFill(0, 0, 0);
            template.Fill();

            return template;
        }
    }
}

در اين مثال، با كمك متد CreateTemplate مرتبط با Canvas دريافتي، يك قالب جديد ايجاد و سپس روي آن نقاشي خواهيم كرد. اكنون مي‌توان از اين قالب تهيه شده، يك Image دريافت كرده و سپس مثلا در سلول‌هاي يك جدول نمايش داد. اينبار خروجي نهايي ما به شكل زير خواهد بود:



خلاصه اشتراک‌های روز جمعه 22 مهر 1390


۱۳۹۰/۰۷/۲۱

SVN 1.7 و حذف پوشه‌هاي .svn اضافي


يكي از ايراداتي كه عموما به SVN مي‌گيرند اين است كه «اگر پروژه‌ي ما 1000 پوشه هم داشته باشد، SVN به ازاي هر كدام، يك پوشه‌ي مخفي به نام .svn را داخل آن‌ها ايجاد مي‌كند و ما اين رو دوست نداريم!». خبر خوب اينكه اين طراحي قديمي، با ارائه SVN 1,7 كلا برطرف شده و اكنون SVN همانند اكثر ورژن كنترل‌هاي ديگر فقط يك پوشه مديريتي را در ريشه‌ي اصلي پروژه‌ي شما ايجاد مي‌كند. به علاوه اينبار از يك بانك اطلاعاتي سريع SQLite هم جهت ذخيره سازي اطلاعات متاديتا مورد نياز خود كمك مي‌گيرد.


نحوه‌ي ارتقاء به اين نگارش جديد هم بسيار ساده است:
اگر از VisualSVN Server استفاده مي‌كنيد، روي گره Repositories در كنسول مديريتي آن كليك راست كرده و سپس از طريق گزينه‌ي All tasks ، عمليات Upgrade repositories format را انتخاب كنيد. در اينجا مي‌شود مخزن‌هاي موجود مورد نظر را انتخاب كرده و ارتقاء داد.


سپس اگر از TortoiseSVN استفاده مي‌كنيد، جايي داخل پوشه جاري پروژه، كليك راست كرده و گزينه‌ي "SVN upgrade working copy" را انتخاب كنيد. بديهي است جهت دسترسي به اين قابليت جديد بايد SVN 1.7 (كه به همراه VisualSVN Server 2.5 هم ارائه شده) و TortoiseSVN 1.7 را پيشتر نصب كرده باشيد.



آشنايي با Refactoring - قسمت 8


يكي از اشتباهاتي كه همه‌ي ما كم و بيش به آن دچار هستيم ايجاد كلاس‌هايي هستند كه «زياد مي‌دانند». اصطلاحا به آن‌ها God Classes هم مي‌گويند و براي نمونه، پسوند يا پيشوند Util دارند. اين نوع كلاس‌ها اصل SRP را زير سؤال مي‌برند (Single responsibility principle). براي مثال يك فايل ايجاد مي‌شود و داخل آن از انواع و اقسام متدهاي «كمكي» كار با ديتابيس تا رسم نمودار تا تبديل تاريخ ميلادي به شمسي و ... در طي بيش از 10 هزار سطر قرار مي‌گيرند. يا براي مثال گروه بندي‌هاي خاصي را در اين يك فايل از طريق كامنت‌هاي نوشته شده براي قسمت‌هاي مختلف مي‌توان يافت. Refactoring مرتبط با اين نوع كلاس‌هايي كه «زياد مي‌دانند»، تجزيه آن‌ها به كلاس‌هاي كوچكتر، با تعداد وظيفه‌ي كمتر است.
به عنوان نمونه كلاس CustomerService زير، دو گروه كار متفاوت را انجام مي‌دهد. ثبت و بازيابي اطلاعات ثبت نام يك مشتري و همچنين محاسبات مرتبط با سفارشات مشتري‌ها:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.Before
{
    public class CustomerService
    {
        public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
        {
            // do work
            throw new NotImplementedException();
        }

        public bool CustomerIsValid(string customer, int order)
        {
            // do work
            throw new NotImplementedException();
        }

        public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
        {
            // do work
            throw new NotImplementedException();
        }

        public void Register(string customer)
        {
            // do work
        }

        public void ForgotPassword(string customer)
        {
            // do work
        }
    }
}

بهتر است اين دو گروه، به دو كلاس مجزا بر اساس وظايفي كه دارند، تجزيه شوند. به اين ترتيب نگهداري اين نوع كلاس‌هاي كوچكتر در طول زمان ساده‌تر خواهند شد:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.After
{
    public class CustomerOrderService
    {
        public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
        {
            // do work
            throw new NotImplementedException();
        }

        public bool CustomerIsValid(string customer, int order)
        {
            // do work
            throw new NotImplementedException();
        }

        public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
        {
            // do work
            throw new NotImplementedException();
        }
    }
}

namespace Refactoring.Day8.RemoveGodClasses.After
{
    public class CustomerRegistrationService
    {
        public void Register(string customer)
        {
            // do work
        }

        public void ForgotPassword(string customer)
        {
            // do work
        }
    }
}

خلاصه اشتراک‌های روز چهار شنبه 20 مهر 1390