مطالب
پشتیبانی توکار از ایجاد کلاس‌های Singleton از دات نت 4 به بعد
روش‌های زیادی برای ایجاد یک وهله‌ی Singleton وجود دارند. وهله‌ای که در طول عمر یک برنامه، تنها یکبار ایجاد شده و حفظ می‌شود. برای مثال شاید متداول‌ترین حالت آن که در بسیاری از کدها دیده می‌شود، تعریف یک متغیر استاتیک در کلاس، غیرعمومی تعریف کردن سازنده‌ی کلاس و وهله سازی این فیلد استاتیک در صورت نال بودن آن است:
    public class WrongSingleton
    {
        static WrongSingleton _instance;

        WrongSingleton()
        {
        }

        public static WrongSingleton Instance
        {
            get { return _instance ?? (_instance = new WrongSingleton()); }
        }
    }
هرچند این روش کار می‌کند اما thread-safe نیست. به این معنا که ممکن است دو ترد در آن واحد به بررسی قسمت ?? instance_ بپردازند و چون هنوز نال است، دوبار وهله سازی کلاس، با فراخوانی new WrongSingleton صورت خواهد گرفت و هر ترد در آن لحظه به وهله‌ی متفاوتی دسترسی خواهد داشت.
راه حل‌های زیادی برای رفع این مشکل با اعمال مباحث قفل گذاری تا نکات ریز مربوط به کامپایلر وجود دارند که لیست آن‌ها را در اینجا می‌توانید مطالعه کنید.

از دات نت 4 به بعد با معرفی الگوی Lazy، امکان پیاده سازی lazy thread safe singletons به صورت توکار در دسترس می‌باشد. نمونه‌ای از آن در کدهای IoC Container معروفی به نام StructureMap بکار رفته‌است:
    public class Container
    {
        // ...
    }

    public static class ObjectFactory
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);

        public static Container Container
        {
            get { return _containerBuilder.Value; }
        }

        private static Container defaultContainer()
        {
            return new Container();
        }
    }
در اینجا کلاس ObjectFactory یک وهله از کلاس Container را در اختیار مصرف کننده قرار می‌دهد؛ با این شرایط:
- چون این وهله توسط کلاس Lazy محصور شده‌است، صرفا در اولین بار دسترسی به آن، نمونه سازی خواهد شد. این مورد سبب کاهش مصرف حافظه‌ی برنامه و همچنین بالا رفتن سرعت برپایی اولیه‌ی آن می‌شود. بسیاری از اشیایی که در یک برنامه تعریف می‌شوند، شاید الزاما جهت ارائه راه حلی برای مساله‌ای خاص، مورد استفاده قرار نگیرند. تعریف آن‌ها به صورت Lazy، سربار نمونه سازی الزامی آن‌ها را حذف خواهد کرد و آن‌را به اولین بار استفاده از شیء مورد نظر، به تعویق خواهد انداخت.
- با استفاده از LazyThreadSafetyMode.ExecutionAndPublication، چندین ترد می‌توانند به خاصیت Container دسترسی پیدا کنند، اما تنها یکی از آن‌ها موفق به وهله سازی این کلاس خواهد شد. البته حالت ExecutionAndPublication، حالت پیش فرض است و الزاما نیازی به ذکر آن نیست.

اینبار بازنویسی کلاس ابتدای بحث با توجه به نکات ذکر شده به صورت زیر خواهد بود:
    public sealed class LazySingleton
    {
        private static readonly Lazy<LazySingleton> _instance =
            new Lazy<LazySingleton>(() => new LazySingleton(), LazyThreadSafetyMode.ExecutionAndPublication);

        private LazySingleton()
        {
        }

        public static LazySingleton Instance
        {
            get { return _instance.Value; }
        }
    }
- در آن سازنده‌ی کلاس، خصوصی تعریف شده‌است.
- تنها وهله‌ی در دسترس کلاس، به صورت استاتیک و نمونه سازی کلاس، توسط کلاس Lazy با پارامتر LazyThreadSafetyMode.ExecutionAndPublication انجام می‌شود.
- علت استفاده از lambda در سازنده‌ی کلاس Lazy، امکان دسترسی به اعضای private کلاس، از طریق یک خاصیت static است.
نظرات مطالب
متدی برای بررسی صحت کد ملی وارد شده
سلام
من از یک مبدل آنلاین استفاده کردم به این نتیجه رسیدم.
Namespace ConsoleApplicationTest
Class Program
Private Shared Sub Main(args As String())
Console.WriteLine("0172942284 => {0}", "0172942284".IsValidNationalCode())
Console.WriteLine("1000000001 => {0}", "1000000001".IsValidNationalCode())
End Sub
End Class

Public NotInheritable Class Helpers
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function IsValidNationalCode(nationalCode As [String]) As [Boolean]
If [String].IsNullOrEmpty(nationalCode) Then
Throw New Exception("لطفا کد ملی را صحیح وارد نمایید")
End If

If nationalCode.Length <> 10 Then
Throw New Exception("طول کد ملی باید ده کاراکتر باشد")
End If

Dim regex__1 = New Regex("[^0-9]<span> </span>")
If Not regex__1.IsMatch(nationalCode) Then
Throw New Exception("کد ملی تشکیل شده از ده رقم عددی می‌باشد؛ لطفا کد ملی را صحیح وارد نمایید")
End If

If Not Regex.IsMatch(nationalCode, "^(?!(\d)\1{9})\d{10}$") Then
Return False
End If

Dim check = Convert.ToInt32(nationalCode.Substring(9, 1))
Dim result = Enumerable.Range(0, 9).[Select](Function(x) Convert.ToInt32(nationalCode.Substring(x, 1)) * (10 - x)).Sum() Mod 11

Dim remainder As Integer = result Mod 11
Return check = (If(remainder < 2, remainder, 11 - remainder))

End Function
End Class
End Namespace
مطالب
آشنایی با الگوی طراحی Iterator
فرض کنید قبلا کلاسی بنام CollectionClass را داشته‌اید که در آن یک آرایه از نوع []String تعریف کرده‌اید. همچنین n تا کلاس هم دارید که از آرایه‌ی تعریف شده‌ی در CollectionClass استفاده می‌کنند. تا اینجا مشکلی نیست. مشکل زمانی شروع می‌شود که متوجه می‌شوید دیگر این آرایه کارآیی ندارد و باید آن را با <List<string جایگزین کنید. واضح است که نمی‌توانید همه کلاس‌هایی را که از CollectionClass استفاده کرده‌اند، بیابید و آنها را تغییر دهید؛ چرا که شاید برخی از کلاس‌ها اصلا در دسترس شما نباشند یا هر دلیل دیگری.
راهگشای این مشکل، استفاده از الگوی طراحی Iterator است. در این الگو، باید کلاس CollectionClass ابتدا واسط IEnumerable را پیاده سازی نماید. این واسط متدی بنام GetEnumerator دارد که می‌توان به کمک آن، درون آرایه یا هر نوع کالکشن دیگری حرکت کرده و آیتم‌های آن را برگرداند.(مطالعه بیشتر )
اول این الگو را پیاده سازی می‌کنیم و در ادامه توضیح می‌دهیم که چگونه مشکل ما را حل میکند:
ابتدا باید کلاس CollectionClass واسط IEnumerable را پیاده سازی نماید. در ادامه بدنه متد GetEnumerator را می‌نویسیم:
    public class CollectionClass : IEnumerable
    {
        private string[] mySet = { "Array of String 1", "Array of String 2", "Array of String 3" };
        public IEnumerator GetEnumerator()
        {
            //return arrayStrings.GetEnumerator(); 
            foreach (var element in mySet )
            {
                yield return element;
            }
        }
    }
در اینجا یک آرایه رشته‌ای را بنام mySet  تعریف کرده‌ایم و مقادیر مختلفی را در آن قرار داده‌ایم. سپس در متد GetEnumerator اعضای این آرایه را خوانده و return می‌کنیم.(yield چیست؟ )
وقتی از این کلاس می‌خواهیم استفاده کنیم، داریم:
CollectionClass c = new CollectionClass();
foreach (var element in c)
{
       Console.WriteLine(element);
}
در این حالت مهم نیست که مجموعه‌ی مورد نظر، آرایه هست یا هر نوع کالکشن دیگری. لذا وقتی بخواهیم نوع mySet را تغییر دهیم، نگران نخواهیم بود؛ چراکه فقط کافی‌است کلاس CollectionClass را تغییر دهیم. بصورت زیر:
 public class CollectionClass : IEnumerable
    {
        //private readonly string[] arrayStrings = { "Array of String 1", "Array of String 2", "Array of String 3" };
        private List<string> mySet= new List<string>() { "Array of String 1", "Array of String 2", "Array of String 3" }; 
        public IEnumerator GetEnumerator()
        {
            foreach (var element in mySet )
            {
                yield return element;
            }
        }
    }
مطالب
PersianDateTime جایگزینی برای System.DateTime
همانطور که در توضیح پروژه PersianDateTime آمده است، کلاس PersianDateTime جایگزینی است برای System.DateTime برای استفاده در پروژه‌هایی که احتیاج به تاریخ شمسی و ساعت رسمی ایران یا سایر کشورهای فارسی‌زبان، مستقل از Time Zone سیستم و در نظر گرفتن Daylight Saving Time، دارند. این کلاس شامل اکثر متدها، پراپرتی‌ها و عملگرهای متداول  System.DateTime است.

دسترسی به تاریخ و ساعت فعلی :
PersianDateTime now = PersianDateTime.Now;

string persianDateTime = now.ToString(); // 1392/03/09 23:37:57
string persianDate = now.ToString(PersianDateTimeFormat.Date); // 1392/03/09
string persianFullDateTime = now.ToString("dddd d MMMM yyyy ساعت hh:mm:ss tt"); // پنج شنبه 9 خرداد 1392 ساعت 11:37:57 ب.ظ

TimeSpan persianTime = now.TimeOfDay; // 23:37:57.4641984


نحوه محاسبه PersianDateTime.Now بر اساس فیلد استاتیک PersianDateTime.Mode است که مقدار پیش‌فرض آن PersianDateTimeMode.UtcOffset است.
PersianDateTime.Mode را می‌توان یکی از سه مقدار زیر قرار داد :
  • System : بر اساس تاریخ و زمان سیستم (Time Zone فعلی سیستم)
  • PersianTimeZoneInfo : بر اساس Time Zone تعیین شده در فیلد استاتیک PersianDateTime.PersianTimeZoneInfo (مستقل از Time Zone سیستم)
  • UtcOffset : بر اساس اختلاف ساعت با UTC، مشخص شده در فیلد استاتیک PersianDateTime.OffsetFromUtc (مستقل از Time Zone سیستم)

مقدار پیش‌فرض PersianDateTime.PersianTimeZoneInfo برابر Iran Standard Time Zone است. توجه داشته باشید که در این حالت از DaylightSavingTime تعیین شده در Time Zone استفاده خواهد شد که مثلا برای ایران با زمان واقعی آن اختلاف دارد و باید آنرا اصلاح کرد .

مقدار پیش‌فرض PersianDateTime.OffsetFromUtc برابر 3 ساعت و نیم است. در این حالت DaylightSavingTime با توجه به مقادیر سه فیلد استاتیک DaylightSavingTimeStart ،DaylightSavingTimeEnd و DaylightSavingTime تعیین می‌شود که به صورت پیش‌فرض برابر ساعت 24 اول فروردین، ساعت 24 سی‌ام شهریور و یک ساعت است.

تمام فیلدهای استاتیک ذکر شده به صورت public تعریف شده‌اند تا برنامه‌نویسان سایر کشورهای فارسی‌زبان بتوانند به دلخواه خود آنرا تغییر دهند.

نحوه کار با مقادیر رشته‌ای تاریخ شمسی هم اینگونه است :
PersianDateTime persianDate1 = PersianDateTime.Parse("1392/03/02");
PersianDateTime persianDate2 = PersianDateTime.Parse("1392/03/02", "23:32:56");

چند سازنده هم وجود دارد برای کسانی که تاریخ را به صورت int و ساعت را int یا short (بدون ثانیه) ذخیره می‌کنند :
PersianDateTime persianDate1 = new PersianDateTime(13920310);
PersianDateTime persianDate2 = new PersianDateTime(13920310, 233256);
PersianDateTime persianDate3 = new PersianDateTime(13920310, (short)2332);

تبدیل تاریخ میلادی به شمسی :
DateTime miladiDate = new DateTime(2013, 5, 31);
PersianDateTime persianDate = new PersianDateTime(miladiDate);

تبدیل تاریخ شمسی به میلادی :
PersianDateTime persianDate = PersianDateTime.Parse("1392/03/02");
DateTime miladiDate = persianDate.ToDateTime();


علاوه بر متد ToString معمولی، دو overload دیگر از این متد برای نمایش فرمت‌های مختلف PersianDateTime وجود دارد :

public string ToString(PersianDateTimeFormat format);

public string ToString(string format);
که اولی برای فرمت‌های معمول و دومی برای هر نوع فرمت دلخواه قابل استفاده است که چند نمونه آنرا در قسمت تعیین تاریخ و ساعت فعلی دیدید.

برخی از خصوصیات کلاس PersianDateTime :
  • Year : سال
  • Month : ماه
  • Day : روز
  • TimeOfDay : زمان سپری شده از ابتدای روز
  • TimeOfWeek :زمان سپری شده از ابتدای هفته
  • TimeOfMonth : زمان سپری شده از ابتدای ماه
  • TimeOfYear : زمان سپری شده از ابتدای سال
  • DaysInYear : تعداد روز سال
  • DaysInMonth : تعداد روز ماه
  • DayOfWeek : چندمین روز هفته
  • DayOfYear : چندمین روز سال
  • DayName : نام روز
  • MonthName : نام ماه
  • Date : تاریخ بدون زمان
  • FirstDayOfWeek : اولین روز هفته
  • LastDayOfWeek : آخرین روز هفته
  • FirstDayOfMonth : اولین روز ماه
  • LastDayOfMonth : آخرین روز ماه
  • FirstDayOfYear : اولین روز سال
  • LastDayOfYear : آخرین روز سال

برخی دیگر از متدهای کلاس PersianDateTime :

public PersianDateTime Add(TimeSpan value);
public PersianDateTime AddDays(double value);
public PersianDateTime AddMonths(int months);
public PersianDateTime AddYears(int value);
public PersianDateTime AddHours(double value);
public PersianDateTime AddMinutes(double value);
public PersianDateTime AddSeconds(double value);
همچنین تمام عملگرهای +، -، >، <، =>، =<، ==، و =! قابل استفاده هستند.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing
کلاس مدل
    public class Category
    {
        public int Id { get; set; }

        [StringLength(450)]
        public string Name { get; set; }

        public int? ParentId { get; set; }

        public virtual Category Parent { get; set; }

        public virtual ICollection<Category> Children { get; set; }
    }

    public class CategoryConfiguration : IEntityTypeConfiguration<Category>
    {
        public void Configure(EntityTypeBuilder<Category> builder)
        {
            builder.HasIndex(c => c.ParentId);

            builder.HasOne(c => c.Parent)
                   .WithMany(c => c.Children)
                   .HasForeignKey(c => c.ParentId);
        }
    }
ودر نهایت برای افزودن مایگریشن جدید خطای ذیل
Introducing FOREIGN KEY constraint 'FK_Categories_Categories_ParentId' on table 'Categories' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
مطالب
جستجوی یک property در model یا object

گاهی نیاز هست در بین property‌های model‌های مختلفی که از یک مدل base ارث بری کرده اند، جستجویی را انجام دهیم؛ همانند مدل زیر:

    #region my model

    [KnownType(typeof(model1))]
    [KnownType(typeof(model2))]
    [KnownType(typeof(model3))]
    public class TaskGroupObjects : List<IBaseObject>
    {
        #region sortFields
        class compare : IComparer<IBaseObject>
        {
            public int Compare(IBaseObjectfield1, IBaseObjectfield2)
            {
                return field1.Order - field2.Order;
            }
        }

        public void sortByOrder()
        {
            Sort(new compare());
        }
        #endregion
    }
اینکه model، هیچ property ای که منجر به مشخص شدن type آن بشود را نداشته باشد هم آزار دهنده هست. متد زیر مدل را بررسی و در بین property‌های مدل پاس داده شده اگر property مد نظر ما را پیدا کند، مقدار true را برمیگرداند:
public static bool HasProperty(this object your model, string propertyName)
{
    return obj.GetType().GetProperty(propertyName) != null;
}
مطالب
نحوه‌ی نگاشت فیلدهای فرمول در Fluent NHibernate

اگر با SQL Server کار کرده باشید حتما با مفهوم و امکان Computed columns (فیلدهای محاسبه شده) آن آشنایی دارید. چقدر خوب می‌شد اگر این امکان برای سایر بانک‌های اطلاعاتی که از تعریف فیلدهای محاسبه شده پشتیبانی نمی‌کنند، نیز مهیا می‌شد. زیرا یکی از اهداف مهم استفاده‌ی صحیح از ORMs ، مستقل شدن برنامه از نوع بانک اطلاعاتی است. برای مثال امروز می‌خواهیم با MySQL‌ کار کنیم، ماه بعد شاید بخواهیم یک نسخه‌ی سبک‌تر مخصوص کار با SQLite را ارائه دهیم. آیا باید قسمت دسترسی به داده برنامه را از نو بازنویسی کرد؟ اینکار در NHibernate فقط با تغییر نحوه‌ی اتصال به بانک اطلاعاتی میسر است و نه بازنویسی کل برنامه (و صد البته شرط مهم و اصلی آن هم این است که از امکانات ذاتی خود NHibernate استفاده کرده باشید. برای مثال وسوسه‌ی استفاده از رویه‌های ذخیره شده را فراموش کرده و به عبارتی ORM مورد استفاده را به امکانات ویژه‌ی یک بانک اطلاعاتی گره نزده باشید).
خوشبختانه در NHibernate امکان تعریف فیلدهای محاسباتی با کمک تعریف نگاشت خواص به صورت فرمول مهیا است. برای توضیحات بیشتر لطفا به مثال ذیل دقت بفرمائید:
در ابتدا کلاس کاربر تعریف می‌شود:

using System;
using NHibernate.Validator.Constraints;

namespace FormulaTests.Domain
{
public class User
{
public virtual int Id { get; set; }

[NotNull]
public virtual DateTime JoinDate { set; get; }

[NotNullNotEmpty]
[Length(450)]
public virtual string FirstName { get; set; }

[NotNullNotEmpty]
[Length(450)]
public virtual string LastName { get; set; }

[Length(900)]
public virtual string FullName { get; private set; } //از طریق تعریف فرمول مقدار دهی می‌گردد

public virtual int DayOfWeek { get; private set; }//از طریق تعریف فرمول مقدار دهی می‌گردد
}
}
در این کلاس دو خاصیت FullName و DayOfWeek به صورت فقط خواندنی به کمک private set ذکر شده، تعریف گردیده‌اند. قصد داریم روی این دو خاصیت فرمول تعریف کنیم:

using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace FormulaTests.Domain
{
public class UserCustomMappings : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(u => u.Id).GeneratedBy.Identity(); //ضروری است
mapping.Map(x => x.DayOfWeek).Formula("DATEPART(dw, JoinDate) - 1");
mapping.Map(x => x.FullName).Formula("FirstName + ' ' + LastName");
}
}
}
نحوه‌ی انتساب فرمول‌های مبتنی بر SQL را در نگاشت فوق ملاحظه می‌نمائید. برای مثال FullName از جمع دو فیلد نام و نام خانوادگی حاصل خواهد شد و DayOfWeek از طریق فرمول SQL دیگری که ملاحظه می‌نمائید (یا هر فرمول SQL دلخواه دیگری که صلاح می‌دانید).
اکنون اگر Fluent NHibernate را وادار به تولید اسکریپت متناظر با این دو کلاس کنیم حاصل به صورت زیر خواهد بود:
    create table Users (
UserId INT IDENTITY NOT NULL,
JoinDate DATETIME not null,
FirstName NVARCHAR(450) not null,
LastName NVARCHAR(450) not null,
primary key (UserId)
)
همانطور که ملاحظه می‌کنید در اینجا خبری از دو فیلد محاسباتی تعریف شده نیست. این فیلدها در تعاریف نگاشت‌ها به صورت خودکار ظاهر می‌شوند:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true"
name="FormulaTests.Domain.User, FormulaTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Users">
<id name="Id" type="System.Int32" unsaved-value="0">
<column name="UserId" />
<generator class="identity" />
</id>
<property name="DayOfWeek" formula="DATEPART(dw, JoinDate) - 1" type="System.Int32" />
<property name="FullName" formula="FirstName + ' ' + LastName" type="System.String" />
<property name="JoinDate" type="System.DateTime">
<column name="JoinDate" />
</property>
<property name="FirstName" type="System.String">
<column name="FirstName" />
</property>
<property name="LastName" type="System.String">
<column name="LastName" />
</property>
</class>
</hibernate-mapping>
اکنون اگر کوئری زیر را در برنامه اجرا نمائیم:
var list = session.Query<User>.ToList();
foreach (var item in list)
{
Console.WriteLine("{0}:{1}", item.FullName, item.DayOfWeek);
}
به صورت خودکار به SQL ذیل ترجمه خواهد شد و اکنون نحوه‌ی بکارگیری فیلدهای فرمول، بهتر مشخص می‌گردد:
select
user0_.UserId as UserId0_,
user0_.JoinDate as JoinDate0_,
user0_.FirstName as FirstName0_,
user0_.LastName as LastName0_,
DATEPART(user0_.dw, user0_.JoinDate) - 1 as formula0_, --- همان فرمول تعریف شده است
user0_.FirstName + ' ' + user0_.LastName as formula1_ ---از طریق فرمول تعریف شده حاصل گردیده است
from
Users user0_

مطالب
آشنایی با Refactoring - قسمت 1

کارهای سورس باز قابل توجهی از برنامه نویس‌های ایرانی یافت نمی‌شوند؛ عموما کارهای ارائه شده در حد یک سری مثال یا کتابخانه‌های کوچک است و در همین حد. یا گاهی هم انگشت شمار پروژه‌هایی کامل. مثل یک وب سایت یا یک برنامه نصفه نیمه دبیرخانه و امثال آن. این‌ها هم خوب است از دیدگاه به اشتراک گذاری اطلاعات، ایده‌ها و هم ... یک مزیت دیگر را هم دارد و آن این است که بتوان کیفیت عمومی کد نویسی را حدس زد.
اگر کیفیت کدها رو بررسی ‌کنید به یک نتیجه‌ی کلی خواهید رسید: "عموم برنامه نویس‌های ایرانی (حداقل این‌هایی که چند عدد کار سورس باز به اشتراک گذاشته‌اند) با مفهومی به نام Refactoring هیچگونه آشنایی ندارند". مثلا یک برنامه‌ی WinForm تهیه کرده‌اند و کل سورس برنامه همان چند عدد فرم برنامه است و هر فرم بالای 3000 سطر کد دارد. دوستان عزیز! به این می‌گویند «فاجعه‌ای به نام کدنویسی!» صاحب اول و آخر این نوع کدها خودتان هستید! شاید به همین جهت باشد که عمده‌ی پروژه‌های سورس باز پس از اینکه برنامه نویس اصلی از توسعه‌ی آن دست می‌کشد، «می‌میرند». چون کسی جرات نمی‌کند به این کدها دست بزند. مشخص نیست الان این قسمت را که تغییر دادم، کجای برنامه به هم ریخت. تستی ندارند. ساختاری را نمی‌توان از آن‌ها دریافت. منطق قسمت‌های مختلف برنامه از هم جدا نشده است. برنامه یک فرم است با چند هزار سطر کد در یک فایل! کار شما شبیه به کد اسمبلی چند هزار سطری حاصل از decompile یک برنامه که نباید باشد!
به همین جهت قصد دارم یک سری «ساده» Refactoring را در این سایت ارائه دهم. روی سادگی هم تاکید کردم، چون اگر عموم برنامه نویس‌ها با همین موارد به ظاهر ساده آشنایی داشتند، کیفیت کد نویسی بهتری را می‌شد در نتایج عمومی شده، شاهد بود.
این مورد در راستای نظر سنجی انجام شده هم هست؛ درخواست مقالات خالص سی شارپ در صدر آمار فعلی قرار دارد.



Refactoring چیست؟

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


قسمت اول - مجموعه‌ها را کپسوله کنید

برای مثال کلاس‌های ساده زیر را در نظر بگیرید:

namespace Refactoring.Day1.EncapsulateCollection
{
public class OrderItem
{
public int Id { set; get; }
public string Name { set; get; }
public int Amount { set; get; }
}
}

using System.Collections.Generic;

namespace Refactoring.Day1.EncapsulateCollection
{
public class Orders
{
public List<OrderItem> OrderItems { set; get; }
}
}

نکته اول: هر کلاس باید در داخل یک فایل جدا قرار گیرد. «لطفا» یک فایل درست نکنید با 50 کلاس داخل آن. البته اگر باز هم یک فایل باشد که بتوان 50 کلاس را داخل آن مشاهده کرد که چقدر هم عالی! نه اینکه یک فایل باشد تا بعدا 50 کلاس را با Refactoring از داخل آن بیرون کشید!

قطعه کد فوق، یکی از روش‌های مرسوم کد نویسی است. مجموعه‌ای به صورت یک List عمومی در اختیار مصرف کننده قرار گرفته است. حال اجازه دهید تا با استفاده از برنامه FxCop برنامه فوق را آنالیز کنیم. یکی از خطاهایی را که نمایش خواهد داد عبارت زیر است:

Error, Certainty 95, for Do Not Expose Generic Lists

بله. لیست‌های جنریک را نباید به همین شکل در اختیار مصرف کننده قرار داد؛ چون به این صورت هر کاری را می‌توانند با آن انجام دهند، مثلا کل آن را تعویض کنند، بدون اینکه کلاس تعریف کننده آن از این تغییرات مطلع شود.
پیشنهاد FxCop این است که بجای List از Collection یا IList و موارد مشابه استفاده شود. اگر اینکار را انجام دهیم اینبار به خطای زیر خواهیم رسید:

Warning, Certainty 75, for Collection Properties Should Be ReadOnly

FxCop پیشنهاد می‌دهد که مجموعه تعریف شده باید فقط خواندنی باشد.

چکار باید کرد؟
بجای استفاده از List جهت ارائه مجموعه‌ها، از IEnumerable استفاده کنید و اینبار متدهای Add و Remove اشیاء به آن‌را به صورت دستی تعریف نمائید تا بتوان از تغییرات انجام شده بر روی مجموعه ارائه شده، در کلاس اصلی آن مطلع شد و امکان تعویض کلی آن‌را از مصرف کننده گرفت. برای مثال:

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

namespace Refactoring.Day1.EncapsulateCollection
{
public class Orders
{
private int _orderTotal;
private List<OrderItem> _orderItems;

public IEnumerable<OrderItem> OrderItems
{
get { return _orderItems; }
}

public void AddOrderItem(OrderItem orderItem)
{
_orderTotal += orderItem.Amount;
_orderItems.Add(orderItem);
}

public void RemoveOrderItem(OrderItem orderItem)
{
var order = _orderItems.Find(o => o == orderItem);
if (order == null) return;

_orderTotal -= orderItem.Amount;
_orderItems.Remove(orderItem);
}
}
}


اکنون اگر برنامه را مجددا با fxCop آنالیز کنیم، دو خطای ذکر شده دیگر وجود نخواهند داشت. اگر این تغییرات صورت نمی‌گرفت، امکان داشتن فیلد _orderTotal غیر معتبری در کلاس Orders به شدت بالا می‌رفت. زیرا مصرف کننده مجموعه OrderItems می‌توانست به سادگی آیتمی را به آن اضافه یا از آن حذف کند، بدون اینکه کلاس Orders از آن مطلع شود یا اینکه بتواند عکس العمل خاصی را بروز دهد.


مطالب
قراردادن کلاس های Metdata در اسمبلی جداگانه برای کار با WCF Ria Service توسط FluentMetadata
اصلی‌ترین مزیت این پکیج ،امکان جداکردن dataModel و Metadata در پروژه یا اسمبلی جداگانه است . در حالیکه WCF RIA Service استاندارد فاقد این قابلیت میباشد و باید dataModel و Metadata در یک پروژه و در یک namespace تعریف شوند. 
برای استفاده از FluentMetadata :
1) ابتدا فرض می‌کنیم که کلاس Product ما در یک اسمبلی دیگر به نام DataModel تعریف شده است ، بصورت زیر :
public class Product
{
        public int ProductId { get; set; }
        public string ProductName { get; set; }
        public long ProductPrice { get; set; }
}
2) حال یک پروژه جدید به نام DataModelsMetadata تعریف می‌کنیم و ارجاعی به اسمبلی بالا یعنی DataModel نیز به آن اضافه می‌کنیم .
2-1) ابتدا باید پکیج FluentMetadata را توسط Nuget نصب کرد. راهنمای نصب
2-2) سپس کلاس‌های Metadata موردنظر خود را برای کلاس Product تعریف میکنیم .
public class ProductMetadata : MetadataClass<Product>
{
    public ProductMetadata ()
    {
        this.Validation(x => x.ProductName).Required("عنوان محصول وارد نشده است");
        this.Validation(x => x.ProductPrice).Range(1000,100000,"قیمت محصول باید بین هزار تومان تا صدهزار تومان باشد");
    }
}
2-3) سپس یک کلاس MetadataConfiguration که برای نمونه سازی تمام کلاس‌های متادیتا ایجاد می‌کنیم.
public class FluentMetadataConfiguration : IFluentMetadataConfiguration
{
    public void OnTypeCreation(MetadataContainer metadataContainer)
    {
        metadataContainer.Add(new ProductMetadata());
    }
}
2-4) در آخر اضافه کردن MetadataConfiguration  ایجاد شده به Domain Service توسط ویژگی FluentMetadata.
[EnableClientAccess()]
[FluentMetadata(typeof(FluentMetadataConfiguration))]
public class FluentMetadataTestDomainService : DomainService
{
    ...
}
منابع :
مطالب
C# 8.0 - Nullable Reference Types
نوع‌های ارجاعی (Reference Types) در #C، همیشه نال‌پذیر بوده‌اند؛ در مقابل نوع‌های مقداری (value types) مانند DateTime که برای نال‌پذیر کردن آن‌ها باید یک علامت سؤال را در حین تعریف نوع آن‌ها ذکر کرد تا تبدیل به یک نوع نال‌پذیر شود (DateTime? Created). بنابراین عنوانی مانند «نوع‌های ارجاعی نال‌نپذیر» شاید آنچنان مفهوم نباشد.
خالق Null در زبان‌های برنامه نویسی، آن‌را یک اشتباه چند میلیارد دلاری می‌داند! و به عنوان یک توسعه دهنده‌ی دات نت، غیرممکن است که در حین اجرای برنامه‌های خود تابحال به null reference exception برخورد نکرده باشید. هدف از ارائه‌ی قابلیت جدید «نوع‌های ارجاعی نال‌نپذیر» در C# 8.0، مقابله‌ی با یک چنین مشکلاتی است و خصوصا غنی سازی IDEها برای ارائه‌ی اخطارهایی پیش از کامپایل برنامه، در مورد قسمت‌هایی از کد که ممکن است سبب بروز null reference exception شوند.


فعالسازی «نوع‌های ارجاعی نال‌نپذیر»

قابلیت «نوع‌های ارجاعی نال‌نپذیر» به صورت پیش‌فرض غیرفعال است. برای فعالسازی آن می‌توان فایل csproj را به صورت زیر، با افزودن خاصیت NullableContextOptions، ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
  </PropertyGroup>
</Project>
یک نکته: در نگارش‌های بعدی NET Core SDK. و همچنین ویژوال استودیو (از نگارش 16.2.0 به بعد)، خاصیت NullableContextOptions به صرفا Nullable تغییر نام یافته و ساده شده‌است. بنابراین اگر در این نگارش‌ها به خطاهای ذیل برخوردید:
CS8632: The annotation for nullable reference types should only be used in code within a ‘#nullable’ context.
CS8627: A nullable type parameter must be known to be a value-type or non-nullable reference type. Consider adding a ‘class’, ‘struct’ or type constraint.
صرفا به معنای استفاده‌ی از نام قدیمی این ویژگی است که باید به Nullable تغییر پیدا کند:
<PropertyGroup>
  <LangVersion>preview</LangVersion>
  <Nullable>enable</Nullable>
</PropertyGroup>
اما در زمان نگارش این مطلب که 3.0.100-preview5-011568 در دسترس است، فعلا همان نام قدیمی NullableContextOptions کار می‌کند.


تغییر ماهیت نوع‌های ارجاعی #C با فعالسازی NullableContextOptions


در #C ای که ما می‌شناسیم، رشته‌ها قابلیت پذیرش نال را دارند و همچنین ذکر آن‌ها به صورت nullable بی‌معنا است. اما پس از فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر، اکنون عکس آن رخ می‌دهد. رشته‌ها نال‌نپذیر می‌شوند؛ اما می‌توان در صورت نیاز، آن‌ها را nullable نیز تعریف کرد.


یک مثال: بررسی تاثیر فعالسازی NullableContextOptions بر روی یک پروژه

کلاس زیر را در نظر بگیرید:
    public class Person
    {
        public string FirstName { get; set; }

        public string MiddleName { get; set; }

        public string LastName { get; set; }

        public Person(string first, string last) =>
            (FirstName, LastName) = (first, last);

        public Person(string first, string middle, string last) =>
            (FirstName, MiddleName, LastName) = (first, middle, last);

        public override string ToString() => $"{FirstName} {MiddleName} {LastName}";
    }
با فعالسازی خاصیت NullableContextOptions، بلافاصله اخطار زیر در IDE ظاهر می‌شود (اگر ظاهر نشد، یکبار پروژه را بسته و مجددا بارگذاری کنید):


در این کلاس، دو سازنده وجود دارند که یکی MiddleName را دریافت می‌کند و دیگری خیر. در اینجا کامپایلر تشخیص داده‌است که چون در سازنده‌ی اولی که MiddleName را دریافت نمی‌کند، مقدار پیش‌فرض خاصیت MiddleName، نال خواهد بود و همچنین ما NullableContextOptions را نیز فعال کرده‌ایم، بنابراین این خاصیت دیگر به صورت معمول و متداول یک نوع ارجاعی نال‌پذیر عمل نمی‌کند و دیگر نمی‌توان نال را به عنوان مقدار پیش‌فرض آن، به آن نسبت داد. به همین جهت اخطار فوق ظاهر شده‌است.
برای رفع این مشکل:
به کامپایلر اعلام می‌کنیم: «می‌دانیم که MiddleName می‌تواند نال هم باشد» و آن‌را در این زمینه راهنمایی می‌کنیم:
public string? MiddleName { get; set; }
پس از این تغییر، اخطار فوق که ذیل سازنده‌ی اول کلاس Person ظاهر شده بود، محو می‌شود. اما اکنون مجددا کامپایلر، در جائیکه می‌خواهیم از آن استفاده کنیم:
    public static class NullableReferenceTypes
    {
        //#nullable enable // Toggle to enable

        public static string Exemplify()
        {
            var vahid = new Person("Vahid", "N");
            var length = GetLengthOfMiddleName(vahid);

            return $"{vahid.FirstName}'s middle name has {length} characters in it.";

            static int GetLengthOfMiddleName(Person person)
            {
                string middleName = person.MiddleName;
                return middleName.Length;
            }
        }
    }
اخطارهایی را صادر می‌کند:


در اینجا در متد محلی (local function) تعریف شده، سعی در دسترسی به خاصیت MiddleName وجود دارد و اکنون با تغییر جدیدی که اعمال کردیم، به صورت نال‌پذیر تعریف شده‌است.
همچنین در سطر بعدی آن نیز نتیجه‌ی نهایی middleName، مورد استفاده قرار گرفته‌است که آن نیز مشکل‌دار تشخیص داده شده‌است.
مشکل اولین سطر را به این صورت می‌توانیم برطرف کنیم:
var middleName = person.MiddleName;
در اینجا بجای ذکر صریح نوع string، از var استفاده شده‌است. پیشتر با ذکر صریح نوع string، آن‌را یک رشته‌ی نال‌نپذیر تعریف کرده بودیم. اما اکنون چون person.MiddleName نال‌پذیر تعریف شده‌است، var نیز به صورت خودکار به این رشته‌ی نال‌پذیر اشاره می‌کند.
اما مشکل سطر دوم هنوز باقی است:


علت اینجا است که متغیر middleName نیز اکنون ممکن است مقدار نال را داشته باشد. برای رفع این مشکل می‌توان از اپراتور .? استفاده کرد و سپس اگر مقدار نهایی این عبارت نال بود، مقدار صفر را بازگشت می‌دهیم:
static int GetLengthOfMiddleName(Person person)
{
   var middleName = person.MiddleName;
   return middleName?.Length ?? 0;
}
هدف از این قابلیت و ویژگی کامپایلر، کمک کردن به توسعه دهنده‌ها جهت نوشتن کدهایی امن‌تر و مقاوم‌تر به null reference exception‌ها است.


امکان خاموش و روشن کردن ویژگی نوع‌های ارجاعی نال‌نپذیر به صورت موضعی

زمانیکه خاصیت NullableContextOptions را فعال می‌کنیم، بر روی کل پروژه تاثیر می‌گذارد. برای مثال اگر یک چنین قابلیتی را بر روی پروژه‌های قدیمی خود فعال کنید، با صدها اخطار مواجه خواهید شد. به همین جهت است که این ویژگی حتی با فعالسازی C# 8.0 و انتخاب آن، به صورت پیش‌فرض غیرفعال است. بنابراین برای اینکه بتوان پروژه‌های قدیمی را قدم به قدم و سر فرصت، «مقاوم‌تر» کرد، می‌توان تعیین کرد که کدام قسمت، تحت تاثیر این ویژگی قرار بگیرد و کدام قسمت‌ها خیر:
public static class NullableReferenceTypes
{
#nullable disable // Toggle to enable
در اینجا می‌توان با استفاده از compiler directive جدید nullable# به کامپایلر اعلام کرد که از این قسمت صرفنظر کن. مقدار آن می‌تواند disable و یا enable باشد.


مجبور ساختن خود به «مقاوم سازی» برنامه

اگر NullableContextOptions را فعال کنید، کامپایلر صرفا یکسری اخطار را در مورد مشکلات احتمالی صادر می‌کند؛ اما برنامه هنوز کامپایل می‌شود. برای اینکه خود را مقید به «مقاوم سازی» برنامه کنیم، می‌توانیم با فعالسازی ویژگی TreatWarningsAsErrors در فایل csprj، این اخطارها را تبدیل به خطای کامپایلر کرده و از کامپایل برنامه جلوگیری کنیم:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
</Project>
البته TreatWarningsAsErrors تمام اخطارهای برنامه را تبدیل به خطا می‌کند. اگر می‌خواهید انتخابی‌تر عمل کنید، می‌توان از خاصیت WarningsAsErrors استفاده کرد:
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>


آیا اگر برنامه‌ای با C# 7.0 کامپایل شود، کتابخانه‌های تهیه شده‌ی با C# 8.0 را می‌تواند استفاده کند؟

پاسخ: بله. از دیدگاه برنامه‌های قدیمی، کتابخانه‌های تهیه شده‌ی با C# 8.0، تفاوتی با سایر کتابخانه ندارند. آن‌ها نوع‌های نال‌پذیر جدید را مانند ?string مشاهده نمی‌کنند؛ آن‌ها فقط string را مشاهده می‌کنند و روش کار کردن با آن‌ها نیز همانند قبل است. بدیهی است در این حالت از مزایای کامپایلر C# 8.0 در تشخیص زود هنگام مشکلات برنامه محروم خواهند بود؛ اما عملکرد برنامه تفاوتی نمی‌کند.


وضعیت برنامه‌ی C# 8.0 ای که از کتابخانه‌های C# 7.0 و یا قبل از آن استفاده می‌کند، چگونه خواهد بود؟

چون کتابخانه‌های قدیمی‌تر از مزایای کامپایلر C# 8.0 استفاده نمی‌کنند، خروجی‌های آن بدون بروز خطایی توسط کامپایلر C# 8.0 استفاده می‌شوند؛ چون حجم اخطارهای صادر شده‌ی در این حالت بیش از حد خواهد بود. یعنی این بررسی‌های کامپایلر صرفا برای کتابخانه‌های جدید فعال هستند و نه برای کتابخانه‌های قدیمی.


مهارت‌های مواجه شدن با اخطارهای ناشی از فعالسازی NullableContextOptions

در مثالی که بررسی شد، یک نمونه از روش‌های مواجه شدن با اخطارهای ناشی از فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر را بررسی کردیم. در ادامه روش‌های تکمیلی دیگری را بررسی می‌کنیم.

1- هرجائیکه قرار است متغیر ارجاعی نال‌پذیر باشد، آن‌را صراحتا اعلام کنید.
string name = null; // ERROR
string? name = null; // OK!
این مثال را پیشتر بررسی کردیم. با فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر، ماهیت آن‌ها نیز تغییر می‌کند و دیگر نمی‌توان به آن‌ها null را انتساب داد. اگر نیاز است حتما اینکار صورت گیرد، آن‌ها را توسط ? به صورت nullable تعریف کنید.
نمونه‌ی دیگر آن مثال زیر است:
public class Person
{
    public Address? Address { get; set; };
    public string Country => Address?.Country;   // ERROR! 
}
در اینجا Address یک نوع ارجاعی نال‌پذیر است. بنابراین حاصل Address?.Country می‌تواند نال باشد و به Country نال‌نپذیر قابل انتساب نیست. برای رفع این مشکل کافی است دقیقا مشخص کنیم که این رشته نیز نال‌پذیر است:
public class Person
{
    public Address? Address { get; set; };
    public string? Country => Address?.Country;  // OK!
}

البته در این حالت باید به مثال زیر دقت داشت:
var node = this; // Initialize non-nullable variable
while (node != null)
{
   node = null; // ERROR!
}
چون node در اینجا توسط var تعریف شده‌است، دقیقا نوع this را که non-nullable است، پیدا می‌کند. بنابراین بعدها نمی‌توان به آن null را انتساب داد. اگر چنین موردی نیاز بود، باید صریحا نوع آن‌را بدو امر، nullable تعریف کرد؛ چون هنوز امکان تعریف ?var میسر نیست:
Node? node = this;   // Initialize nullable variable
while (node != null) {
   node = null; // OK!
}


2- نوع‌های خود را مقدار دهی اولیه کنید.
در مثال زیر:
public class Person
{
   public string Name { get; set; } // ERROR!
}
در این حالت چون خاصیت Name، در سازنده‌ی کلاس مقدار دهی اولیه نشده‌است، یک اخطار صادر می‌شود که بیانگر احتمال نال بودن آن است. یک روش مواجه شدن با این مشکل، تعریف آن به صورت یک خاصیت نال‌پذیر است:
public class Person
{
   public string? Name { get; set; }
}

یا یک استثناء را صادر کنید:
public class Person
{
    public string Name { get; set; }
    public Person(string name) {
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
}
به این ترتیب کامپایلر می‌داند که اگر نام دریافتی نال بود، دقیقا باید چگونه رفتار کند.
البته در این حالت برای مقدار دهی اولیه‌ی Name، حتما نیاز به تعریف یک سازنده‌است و در این حالت کدهایی را که از سازنده‌ی پیش‌فرض استفاده کرده بودند (مانند new Person { Name = "Vahid" })، باید تغییر دهید.

راه‌حل دیگر، مقدار دهی اولیه‌ی این خواص بدون تعریف یک سازنده‌ی اضافی است:
public class Person
{
   public string Name { get; set; } = string.Empty;
   // -or-
   public string Name { get; set; } = "";
}
برای مثال می‌توان از مقادیر خالی زیر برای مقدار دهی اولیه‌ی رشته‌ها، آرایه‌ها و مجموعه‌ها استفاده کرد:
String.Empty
Array.Empty<T>()
Enumerable.Empty<T>()
یا حتی می‌توان اشیاء دیگر را نیز به صورت زیر مقدار دهی اولیه کرد:
public class Person
{
   public Address Address { get; set; } = new Address();
}
البته در این حالت باید مفهوم فلسفی «خالی بودن» را پیش خودتان تفسیر و تعریف کنید که دقیقا مقصود از یک آدرس خالی چیست؟ به همین جهت شاید تعریف این شیء به صورت nullable بهتر باشد.