نظرات مطالب
C# 12.0 - Collection Expressions & Spread Operator
اصلاحیه: کارآیی spread operator بیشتر نیست!

در متن در مورد spread operator عنوان شده «که ... نگارش C# 12 آن کارآیی بیشتری دارد». این مورد بدون توجه به #Low-Level C تولیدی، نوشته شد و ... متاسفانه نادرست است!
برای مثال فرض کنید، چنین متدی را دارید که با استفاده از spread operator، کار بازکردن یک آرایه را انجام می‌دهد:
public int[] WithSpread()
{
   int[] data = new int[10_000];
   int[] results = [..data];
   return results;
}
معادل #Low-Level C آن (کد نهایی که کامپایلر برای تبدیل آن به IL تولید می‌کند) به صورت زیر است ( #Low-Level C را در Rider، در منوی #Tools -> IL Viewer -> Select Low-Level C می‌توانید تولید کنید):
public int[] WithSpread()
{
  int[] numArray1 = new int[10000];
  int index1 = 0;
  int[] numArray2 = new int[numArray1.Length];
  int[] numArray3 = numArray1;
  for (int index2 = 0; index2 < numArray3.Length; ++index2)
  {
    int num = numArray3[index2];
    numArray2[index1] = num;
    ++index1;
  }
  return numArray2;
}
همانطور که مشاهده می‌کنید، این قطعه کد در C#12 و دات‌نت 8، به شدت ابتدایی تولید شده و به همراه هیچ نوع بهینه سازی نیست. کارآیی این قطعه کد، نسبت به زمانیکه از متد قدیمی CopyTo آرایه‌ها استفاده می‌شود، به مراتب کمتر است (تا 3 برابر!)؛ چون متد CopyTo به همراه بهینه سازی‌های سخت‌افزاری هم هست. به نظر قرار شده بهینه سازی کارآیی spread operator در نگارش بعدی دات‌نت انجام شود.

برای آزمایش شخصی آن، از کلاس زیر استفاده کنید:
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SpreadBenchmark
{
    [MemoryDiagnoser]
    public class Tests
    {
        private readonly int[] _myData = new int[10_000];

        [Benchmark(Baseline = true)]
        public int[] WithToArray()
        {
            int[] results = _myData.ToArray();
            return results;
        }

        [Benchmark]
        public int[] WithCopyTo()
        {
            int[] results = new int[_myData.Length];
            _myData.CopyTo(results, 0);
            return results;
        }

        [Benchmark]
        public int[] WithSpread()
        {
            int[] results = [.._myData];
            return results;
        }
    }
}
که در فایل Program.cs به این صورت فراخوانی می‌شود:
using BenchmarkDotNet.Running;
using SpreadBenchmark;

BenchmarkRunner.Run<Tests>();
با این وابستگی:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
  </ItemGroup>
</Project>
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه
- Collation بانک اطلاعاتی ایجاد شده، بر اساس collation «کل سرور» تنظیم می‌شود. برای مثال بر روی سیستم من Server collation به Persian_100_CI_AS تنظیم شده‌است. بنابراین بانک اطلاعاتی ایجاد شده‌ی در اینجا هم دقیقا همین Collation را دارد (امتحان کردم).
/* Identify SQL Server Collation Settings*/
USE Master
GO
SELECT SERVERPROPERTY('collation') AS SQLServerCollation
GO
 - Collation، ارتباطی به نحوه‌ی ذخیره شدن اطلاعات یونیکد ندارد. بیشتر هدف آن sort صحیح اطلاعات است و همچنین مشخص سازی نحوه‌ی مقایسه‌ی عبارات و حساس بودن به بزرگی و کوچکی حروف. شما در Collation فارسی، هم ی فارسی و هم ی عربی را می‌توانید بدون مشکل ثبت کنید. اگر این Collation وجود نداشت، شبیه به SQLite، در مرتب سازی حروف فارسی، عملیات نهایی بر اساس کد اسکی آن‌ها انجام می‌شد و در این حالت مثلا «پ» را در انتهای لیست مشاهده می‌کردید.
 - اگر مشکل ثبت اطلاعات یونیکد را داشت، شما در بانک اطلاعاتی فقط ????? را مشاهده می‌کردید و نه هیچ چیز دیگری را و نه اینکه قسمتی درست ثبت شود و قسمتی نادرست.
 - ویژوال استودیو عموما بر مبنای «تنظیمات محلی سیستم عامل شما» فایل‌ها را ذخیره می‌کند. اگر می‌خواهید این مورد را همواره به UTF8 تغییر دهید، از افزونه‌ی ForceUTF8 (with BOM) استفاده کنید.
مطالب
لینک‌های هفته آخر آذر

وبلاگ‌ها و سایت‌های ایرانی


امنیت


Visual Studio


ASP. Net


طراحی وب


PHP

  • Aptana PHP 1.0 منتشر شد (اگر قبلا این IDE بسیار قابل توجه را دریافت کرده بودید فقط کافی است به منوی aptana و گزینه my aptana مراجعه کرده و از قسمت plugins ، این پلاگین 18 مگابایتی را دریافت کنید.)

اس‌کیوال سرور


سی شارپ


عمومی دات نت


ویندوز


متفرقه


مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 1

ثبت و نگه‌داری تاریخ خورشیدی در SQL Server از دیرباز یکی از نگرانی‌های برنامه‌نویسان و طراحان پایگاه داده‌ها بوده است. در این نوشتار، راه‌کار تعریف یک DataType در SQL Server 2012 به روش CLR آموزش داده خواهد شد.

در ویژوال استودیو یک پروژه‌ی جدید از نوع SQL Server Database Project به شکل زیر ایجاد کنید: 

نام پروژه را به یاد تقویم خیام، prgJalaliDate می‌گذارم. در Solution Explorer روی نام پروژه راست‌کلیک کرده، سپس روی Add New Item کلیک کنید. در پنجره‌ی بازشده مطابق شکل SQL CLR C# User Defined Type را برگزینید؛ سپس نام JalaliDateType را برای آن انتخاب کنید.
 

 متن موجود در صفحه‌ی بازشده را کاملاً حذف کرده و با کد زیر جای‌گزین کنید.

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

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

[Serializable()]
[SqlUserDefinedType(Format.Native)]
public struct JalaliDate : INullable
{
    private Int16 m_Year;
    private byte m_Month;
    private byte m_Day;
    private byte m_Hour;
    private byte m_Minute;
    private byte m_Second;
    private bool is_Null;


    public Int16 Year
    {
        get
        {
            return (this.m_Year);
        }
        set
        {
            m_Year = value;
        }
    }

    public byte Month
    {
        get
        {
            return (this.m_Month);
        }
        set
        {
            m_Month = value;
        }
    }

    public byte Day
    {
        get
        {
            return (this.m_Day);
        }
        set
        {
            m_Day = value;
        }
    }

    public byte Hour
    {
        get
        {
            return (this.m_Hour);
        }
        set
        {
            m_Hour = value;
        }
    }

    public byte Minute
    {
        get
        {
            return (this.m_Minute);
        }
        set
        {
            m_Minute = value;
        }
    }

    public byte Second
    {
        get
        {
            return (this.m_Second);
        }
        set
        {
            m_Second = value;
        }
    }

    public bool IsNull
    {
        get
        {
            return is_Null;
        }
    }

    public static JalaliDate Null
    {
        get
        {
            JalaliDate jl = new JalaliDate();
            jl.is_Null = true;
            return (jl);
        }
    }


    public override string ToString()
    {
        if (this.IsNull)
        {
            return "NULL";
        }
        else
        {
            return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2") + " " + this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2");
        }
    }


    public static JalaliDate Parse(SqlString s)
    {
        if (s.IsNull)
        {
            return Null;
        }

        System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar();
        string str = Convert.ToString(s);
        string[] JDate = str.Split(' ')[0].Split('/');

        JalaliDate jl = new JalaliDate();

        jl.Year = Convert.ToInt16(JDate[0]);
        byte MonthsInYear = (byte)pers.GetMonthsInYear(jl.Year);
        jl.Month = (byte.Parse(JDate[1]) <= MonthsInYear ? (byte.Parse(JDate[1]) > 0 ? byte.Parse(JDate[1]) : (byte)1) : MonthsInYear);
        byte DaysInMonth = (byte)pers.GetDaysInMonth(jl.Year, jl.Month); ;
        jl.Day = (byte.Parse(JDate[2]) <= DaysInMonth ? (byte.Parse(JDate[2]) > 0 ? byte.Parse(JDate[2]) : (byte)1) : DaysInMonth);
        if (str.Split(' ').Length > 1)
        {
            string[] JTime = str.Split(' ')[1].Split(':');
            jl.Hour = (JTime.Length >= 1 ? (byte.Parse(JTime[0]) < 23 && byte.Parse(JTime[0]) >= (byte)0 ? byte.Parse(JTime[0]) : (byte)0) : (byte)0);
            jl.Minute = (JTime.Length >= 2 ? (byte.Parse(JTime[1]) < 59 && byte.Parse(JTime[1]) >= (byte)0 ? byte.Parse(JTime[1]) : (byte)0) : (byte)0);
            jl.Second = (JTime.Length >= 3 ? (byte.Parse(JTime[2]) < 59 && byte.Parse(JTime[2]) >= (byte)0 ? byte.Parse(JTime[2]) : (byte)0) : (byte)0);
        }
        else { jl.Hour = 0; jl.Minute = 0; jl.Second = 0; }

        return (jl);
    }

    public SqlString GetDate()
    {
        return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2");
    }

    public SqlString GetTime()
    {
        return this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2");
    }

    public SqlDateTime ToGregorianTime()
    {
        System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar();
        return SqlDateTime.Parse(pers.ToDateTime(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0).ToString());
    }

    public SqlString JalaliDateAdd(SqlString interval, int increment)
    {
        System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar();
        DateTime dt = pers.ToDateTime(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0);
        string CInterval = interval.ToString();
        bool isConvert = true;
        switch (CInterval)
        {
            case "Year":
                dt = pers.AddYears(dt, increment);
                break;
            case "Month":
                dt = pers.AddMonths(dt, increment);
                break;
            case "Day":
                dt = pers.AddDays(dt, increment);
                break;
            case "Hour":
                dt = pers.AddHours(dt, increment);
                break;
            case "Minute":
                dt = pers.AddMinutes(dt, increment);
                break;
            case "Second":
                dt = pers.AddSeconds(dt, increment);
                break;
            default:
                isConvert = false;
                break;
        }

        if (isConvert == true)
        {
            this.Year = (Int16)pers.GetYear(dt);
            this.Month = (byte)pers.GetMonth(dt);
            this.Day = (byte)pers.GetDayOfMonth(dt);
            this.Hour = (byte)pers.GetHour(dt);
            this.Minute = (byte)pers.GetMinute(dt);
            this.Second = (byte)pers.GetSecond(dt);
        }


        return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2") + " " + this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2");
    }
}

از منوهای بالا روی منوی Bulild و سپس گزینه‌ی Publish prgJalaliDate کلیک کتید:

در پنجره‌ی بازشده روی دکمه‌ی Edit کلیک کنید سپس تنظیمات مربوط به اتصال به پایگاه داده را انجام دهید.

روی دکمه‌ی OK کلیک کنید و سپس در پنجره‌ی اولیه، روی دکمه‌ی Publish کلیک کتید:

به همین سادگی، DataType مربوطه در SQL Server 2012 ساخته می‌شود. خبر خوش این‌که شما می‌توانید با راست‌کلیک روی نام پروژه و انتخاب گزینه‌ی Properties در قسمت Project Setting تنظیمات مربوط به نگارش SQL Server را انجام دهید. (از نگارش 2005 به بعد در VS 2012 پشتیبانی می‌شود.)


اکنون زمان آن رسیده است که DataType ایجادشده را در SQL Server 2012 بیازماییم. SQL Server را باز کنید و دستور زیر را در آن اجرا کتید.

USE Northwind

GO

CREATE TABLE dbo.TestTable
(
Id int NOT NULL IDENTITY (1, 1),
TestDate dbo.JalaliDate NULL
)  ON [PRIMARY]
GO
همین‌طور که مشاهده می‌کنید؛ امکان به‌کارگیری DataType تعریف‌شده وجود دارد. 
اکنون چند رکورد درون این جدول درج می‌کنیم:
Insert into TestTable (TestDate) Values ('1392/02/09'),('1392/02/09 22:40'),('1392/12/30 22:40')
پس از اجرای این دستور خطای زیر در پایین صفحه‌ی SQL Server نمایان می‌شود:

این خطا به این خاطر است که CLR را در SQL Server  فعال نکرده ایم. جهت فعال‌کردن CLR دستور زیر را اجرا کنید:
sp_configure 'clr enabled', 1
Reconfigure
بار دیگر دستور درج را اجرا می‌کنیم:
Insert into TestTable (TestDate) Values ('1392/02/09'),('1392/02/09 22:40'),('1392/12/30 22:40')
ملاحظه می‌کنید که داده‌ها در جدول مربوطه ذخیره شده است. در رکورد نخست چون ساعت، دقیقه و ثانیه تعریف نشده است؛ به طور هوشمند صفر درج شده است. در رکورد دوم، ساعت و دقیقه مقدار دارد ولی ثانیه صفر ثبت شده است. و در رکورد سوم چون سال 1392 کبیسه نیست؛ به صورت هوشمند آخرین روز ماه به جای روز ثبت شده است. هرچند می‌توان با دست‌کاری در توابع سی‌شارپ، این قوانین را عوض کرد.

اکنون زمان آن رسیده است که توسط یک پرس‌وجو، همه‌ی توابعی که در سی‌شارپ برای این نوع داده نوشتیم، بیازماییم. پرس‌وجوی زیر را اجرا کنید:
Select TestDate.ToString() as JalaliDateTime,
          TestDate.GetDate() as JalaliDate, TestDate.GetTime() as JalaliTime,
          TestDate.ToGregorianTime() as GregorianTime,
          TestDate.JalaliDateAdd('Day',1) JalaliTomorrow,
          TestDate.Month as JalaliMonth from TestTable
خروجی این پرس‌وجو به شکل زیر خواهد بود:

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

نیازی به گفتن نیست که می‌توانید به سادگی از توابع مربوط به DateTime در SQL Server بهره ببرید. برای مثال برای به دست آوردن فاصله‌ی میان دو روز از پرس‌وجوی زیر استفاده کنید:
Declare @a JalaliDate  = '1392/02/07 00:00:00'
Declare @b JalaliDate = '1392/02/05 00:00:00'

SELECT DATEDIFF("DAY",@b.ToGregorianTime(),@a.ToGregorianTime()) AS DiffDate

شاد و پیروز باشید.
مطالب
خواندنی‌های 16 تیر

اس کیوال سرور

توسعه وب

دات نت فریم ورک

دبلیو پی اف و سیلور لایت

سی و مشتقات

شیرپوینت

کتاب‌های رایگان

مای اس کیوال

متفرقه

وب سرورها

پی اچ پی

مطالب
فیلدهای پویا در NHibernate

یکی از قابلیت‌های جالب NHibernate امکان تعریف فیلدها به صورت پویا هستند. به این معنا که زیرساخت طراحی یک برنامه "فرم ساز" هم اکنون در اختیار شما است! سیستمی که امکان افزودن فیلدهای سفارشی را دارا است که توسط برنامه نویس در زمان طراحی اولیه آن ایجاد نشده‌اند. در ادامه نحوه‌ی تعریف و استفاده از این قابلیت را توسط Fluent NHibernate بررسی خواهیم کرد.

در اینجا کلاسی که قرار است توانایی افزودن فیلدهای سفارشی را داشته باشد به صورت زیر تعریف می‌شود:
using System.Collections;

namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}

Attributes در عمل همان فیلدهای سفارشی مورد نظر خواهند بود. جهت معرفی صحیح این قابلیت نیاز است تا نگاشت آن‌را از نوع dynamic component تعریف کنیم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
و مهم‌ترین نکته‌ی این بحث هم همین نگاشت فوق است.
ابتدا از IgnoreProperty جهت ندید گرفتن Attributes استفاده کردیم. زیرا درغیراینصورت در حالت Auto mapping ، یک رابطه چند به یک به علت وجود IDictionary به صورت خودکار ایجاد خواهد شد که نیازی به آن نیست (یافتن این نکته نصف روز کار برد ....! چون مرتبا خطای An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر می‌شد و مشخص نبود که مشکل از کجاست).
سپس Attributes به عنوان یک DynamicComponent معرفی شده است. در اینجا چهار فیلد سفارشی را اضافه کرده‌ایم. به این معنا که اگر نیاز باشد تا فیلد سفارشی دیگری به سیستم اضافه شود باید یکبار Session factory ساخته شود و SchemaUpdate فراخوانی گردد و خوشبختانه با وجود Fluent NHibernate ، تمام این تغییرات در کدهای برنامه قابل انجام است (بدون نیاز به سر و کار داشتن با فایل‌های XML نگاشت‌ها و ویرایش دستی آن‌ها). از تغییر نام جدول که برای مثال در اینجا tblDynamicEntity در نظر گرفته شده تا افزودن فیلدهای دیگر در قسمت DynamicComponent فوق.
همچنین باتوجه به اینکه این نوع تغییرات (ساخت دوبار سشن فکتوری) مواردی نیستند که قرار باشد هر ساعت انجام شوند، بنابراین سربار آنچنانی را به سیستم تحمیل نمی‌کنند.
   drop table tblDynamicEntity

create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)

اگر با SchemaExport، اسکریپت خروجی معادل با نگاشت فوق را تهیه کنیم به جدول فوق خواهیم رسید. نوع و طول این فیلدهای سفارشی بر اساس نوعی که برای اشیاء دیکشنری مشخص می‌کنید، تعیین خواهند شد.

چند مثال جهت کار با این فیلدهای سفارشی یا پویا :

نحوه‌ی افزودن رکوردهای جدید بر اساس خاصیت‌های سفارشی:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;

savedId = session.Save(obj);
tx.Commit();
}
}

با خروجی
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]

نحوه‌ی کوئری گرفتن از این اطلاعات (فعلا پایدارترین روشی را که برای آن یافته‌ام استفاده از HQL می‌باشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();

if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
با خروجی:
    select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]

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

نحوه‌ی به روز رسانی و حذف اطلاعات بر اساس فیلدهای پویا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}

//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}

مطالب
معرفی پروژه Orchard
معرفی پروژه Orchard:
 سیستم مدیریت محتوای Orchard توسط مایکروسافت در ژانویه سال 2011 همراه با ASP.NET MVC 3, IIS Express, SQL CE 4 ,فریم ورک Web Farm و WebMatrix ارائه شد. هدف تمامی این پروژه‌ها ایجاد قابلیتی برای توسعه آسان برنامه‌های تحت وب در محیط ویندوز بود. همانطور که PHP دارای ابزارهای مناسبی برای این منظور است. با ارائه این ابزارها مایکروسافت درخواست برنامه نویسان را برای ساده سازی تجربه توسعه وب اجابت کرد. پروژه Orchard متعلق به Outercurve Foundation (به ندرت CodePlex Foundation نیز شناخته می‌شود) است که توسط مایکروسافت پشتیبانی می‌شود. Outercurve Foundation یک سازمان غیر انتفاعی است که هدف آن تشویق و حمایت از پروژه‌های متنی بازی نظیر Orchad و یا toolkit معروف ASP.NET MVC یعنی MVC Contrib است. مایکروسافت به صورت رسمی از Orchad پشتیبانی نمی‌کند اما در حال حاضر برنامه نویسانی را جهت توسعه این سیستم استخدام کرده است.

برای پروژه Orchad سه هدف تعیین شده است :
1)فراهم نمودن و به اشتراک گذاری یک مجموعه کامپوننت جهت استفاده در برنامه‌های ASP.NET
2)ساخت تعدادی برنامه‌ی مرجع با استفاده از کامپوننت‌های فوق
3)ساخت انجمن هایی برای پشتیبانی از این کامپوننت‌ها و یا برنامه‌های مرجع

 در حال حاضر Orchard بیشتر به عنوان یک سکو (platform) برای ساخت وب سایت‌های ایجاد محتوی استفاده می‌شود آنچه در Orchard حائز اهمیت است ذکر این نکته است که این سیستم به طور کامل با استفاده از ابزار‌های متن باز نوشته شده است. Orchard از ASP.NET MVC 3.0 به همراه View engine جدید و فوق العاده آن یعنی Razor بهره می‌برد. همچنین این پروژه وابستگی زیادی به دیگر ابزارهای متن باز نظیر NHibernate برای دسترسی به داده‌ها و همچنین Autofac برای dependency injection دارد شایان ذکر است که مجوز استفاده از Orchard تحت لیسانس BSD است.

طبق اعلام وب سایت رسمی این پروژه در عرض حدود یک سالی که از ارائه این CMS می‌گذرد بیش از یک میلیون بار دانلود  و بیش از 300 ماژول و تم برای آن ساخته شده است که در گالری آن در دسترس می‌باشد. Orchard به صورت ریلیز‌های جزئی ارائه می‌شود و جدیدترن نسخه آن در هنگام نوشتن این متن 1.5.1 می‌باشد.

اما چرا به یک CMS دات نتی دیگر نیاز است ؟

تعداد زیادی سیستم‌های مدیریت محتوای تجاری و یا متن باز در طول این سال‌ها با استفاده از دات نت ارائه شده اند. (DotNetNuke (DNN بدون تردید یک از معروفترین و قدرتمندترین آن‌ها است. این CMS در ابتدا با VB.NET نوشته شد و این رویه تا مدت‌ها ادامه داشت تا اینکه در نسخه اخیر به #C تغییر کرد. اگرچه DNN و همچنین پروژه متن باز دیگری به نام Umbraco هر دو محبوب هستند اما با استفاده از WebForm‌ها پیاده سازی شده اند( البته Umbraco در نسخه 5 قصد داشت که از ASP.NET MVC استفاده کند اما علی رغم در دسترس قرار گرفتن این نسخه ظاهرا تیم Umbraco برای تمرکز بیشتر روی نسخه وب فرمی, تصمیم ندارند این پروژه را ادامه دهند.) امروزه وب فرم‌ها همانند گذشته محبوب نیستند به همین دلیل رغبت کمتری برای استفاده از این CMS‌ها  نسبت به قبل وجود دارد. با توجه به شواهد موجود بسیاری از برنامه نویسان دات نتی به سمت ASP.NET MVC مهاجرت کرده اند به همین دلیل سیستم Orchard بر مبنای این تکنولوژی نسبتا جدید دات نت پیاده شده است. با استفاده از Orchard می‌توان یک وب سایت با عملکرد بسیار بالا بدون نوشتن حتی یک خط کد ایجاد نمود. اما مانند هر سیستم مدیریت محتوی دیگری اگر بخواهیم به آن قابلیت هایی را اضافه کنیم که به صورت پیش فرض در آن نیست باید با ساختار آن به خوبی آشنا شویم و همچنین بر ابزارهای مورد نیاز این کار نیز احاطه داشته باشیم. برای دریافت اطلاعات بیشتر می‌توانید به وب سایت رسمی این پروژه در اینجا مراجعه کنید
مطالب
آشنایی با 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 به عنوان یکی از برگه‌های خواص پروژه در دسترس خواهد بود و به کمک آن می‌توان مشخص کرد که برنامه حین رسیدن به این نوع بررسی‌ها چه عکس العملی را باید بروز دهد.

برای مطالعه بیشتر:
مطالب
امکان تعریف ساده‌تر کلاس‌های Immutable در C# 9.0 با معرفی نوع جدید record
در مطلب معرفی خواص init-only، با روش معرفی خواص immutable آشنا شدیم. نوع جدیدی که به C# 9.0 به نام record اضافه شده‌است، قسمتی از آن بر اساس همان خواص init-only کار می‌کند. به همین جهت مطالعه‌ی آن مطلب، پیش از ادامه‌ی بحث جاری، ضروری است.


چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شده‌است؟

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

مزایای وجود Immutability:

- اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده می‌کنند؛ چون چندین thread می‌توانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
- اشیاء Immutable از اثرات جانبی، مانند تغییرات آن‌ها در متدهای مختلف در امان هستند. می‌توانید آن‌ها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکرده‌است.
- کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر می‌کنند. برای مثال NET runtime.، هش رشته‌های تعریف شده‌ی در برنامه را در پشت صحنه نگهداری می‌کند تا مطمئن شود که تخصیص حافظه‌ی اضافی، برای رشته‌های تکراری صورت نمی‌گیرد. نمونه‌ی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی می‌توان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفاده‌ی مجدد قرار داد، بدون اینکه نگران مصرف حافظه‌ی بالای برنامه باشیم.
- کار با اشیاء Immutable به باگ‌های کمتری ختم می‌شود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمت‌های مختلف برنامه، می‌تواند به باگ‌های ناخواسته‌ای منتهی شوند.
- Hash list‌ها که در جهت بهبود کارآیی برنامه‌ها بسیار مورد استفاده قرار می‌گیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.


روش تعریف نوع‌های جدید record

کلاس ساده‌ی زیر را در نظر بگیرید:
public class User
{
   public string Name { set; get; }
}
برای تبدیل آن به یک نوع جدید record فقط کافی است واژه‌ی کلیدی class آن‌را با record جایگزین کنیم (به آن nominal record هم می‌گویند):
public record User
{
   public string Name { set; get; }
}
نحوه‌ی کار با آن و وهله سازی آن نیز دقیقا مانند کلاس‌ها است:
var user = new User();
user.Name = "User 1";
و ... در اینجا امکان انتساب مقداری به خاصیت Name وجود دارد؛ یعنی این خاصیت به صورت پیش‌فرض Immutable نیست.

روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم می‌گویند):
public record User(string Name);
با این‌کار، به صورت خودکار یک record جدید تشکیل می‌شود که به همراه خاصیت Name است؛ چیزی شبیه به record قبلی که تعریف کردیم (به همین جهت نیاز است نام آن‌را شروع شده‌ی با حروف بزرگ درنظر بگیریم). با این تفاوت که این record، اینبار دارای سازنده است و همچنین خاصیت Name آن از نوع init-only است. در این حالت است که کل record به صورت immutable معرفی می‌شود؛ وگرنه روش تعریف یک خاصیت معمولی که از نوع init-only نیست (مانند مثال اول)، سبب بروز Immutability نخواهد شد.

برای کار با رکورد دومی که تعریف کردیم باید سازند‌ه‌ی این record را مقدار دهی کرد:
var user = new User("User 1");
// Error: Init-only property or indexer 'User.Name' can only be assigned
// in an object initializer, or on 'this' or 'base' in an instance constructor
// or an 'init' accessor. [CS9Features]csharp(CS8852)
user.Name = "User 1";
و همانطور که ملاحظه می‌کنید، چون خاصیت Name از نوع init-only است و در سازنده‌ی record تعریف شده مقدار دهی شده‌است، دیگر نمی‌توان آن‌را مقدار دهی مجدد کرد. همچنین در اینجا امکان استفاده‌ی از object initializers مانند new User { Name = "User 1" } نیز وجود ندارد؛ چون به همراه یک سازنده‌ی به صورت خودکار تولید شده‌است که خاصیتی init-only را مقدار دهی کرده‌است.


نوع جدید record چه اطلاعاتی را به صورت خودکار تولید می‌کند؟

روش دوم تعریف recordها اگر در نظر بگیریم:
public record User(string Name);
و در این حالت برنامه را کامپایل کنیم، به کدهای زیر که حاصل از دی‌کامپایل است، می‌رسیم:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using CS9Features;

public class User : IEquatable<User>
{
 protected virtual Type EqualityContract
 {
  [System.Runtime.CompilerServices.NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(User);
  }
 }

 public string Name
 {
  get;
  set/*init*/;
 }

 public User(string Name)
 {
  this.Name = Name;
  base..ctor();
 }

 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("User");
  stringBuilder.Append(" { ");
  if (PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }

 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append((object?)Name);
  return true;
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator !=(User? r1, User? r2)
 {
  return !(r1 == r2);
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator ==(User? r1, User? r2)
 {
  return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
 }

 public override int GetHashCode()
 {
  return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
 }

 public override bool Equals(object? obj)
 {
  return Equals(obj as User);
 }

 public virtual bool Equals(User? other)
 {
  return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name);
 }

 public virtual User <Clone>$()
 {
  return new User(this);
 }

 protected User(User original)
 {
  Name = original.Name;
 }

 public void Deconstruct(out string Name)
 {
  Name = this.Name;
 }
}
این خروجی به صورت خودکار تولید شده‌ی توسط کامپایلر، چنین نکاتی را به همراه دارد:
- record‌ها هنوز هم در اصل همان class‌های استاندارد #C هستند (یعنی در اصل reference type هستند).
- این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
- متد ToString آن بازنویسی شده‌است تا اگر آن‌را بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
- این کلاس از نوع  <IEquatable<User است که امکان مقایسه‌ی اشیاء record را به سادگی میسر می‌کند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شده‌اند (یعنی مقایسه‌ی آن شبیه به value-type است).
- این کلاس امکان clone کردن اطلاعات جاری را مهیا می‌کند.
- همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شده‌ی در آن، به یک tuple مفید است.

بنابراین یک رکورد به همراه قابلیت‌هایی است که سال‌ها در زبان #C وجود داشته‌اند و شاید ما به سادگی حاضر به تشکیل و تکمیل آن‌ها نمی‌شدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آن‌ها را متقبل می‌شود!


ساخت یک وهله‌ی جدید از یک record با clone کردن آن

اگر به کدهای حاصل از دی‌کامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شده‌است:
public virtual User <Clone>$()
{
  return new User(this);
}
زمانیکه یک شیء Immutable است، دیگر نمی‌توان مقادیر خواص آن‌را در ادامه تغییر داد. اما اگر نیاز به اینکار وجود داشت، باید چکار کنیم؟ در C# 9.0 برای ایجاد وهله‌ی جدید معادلی از یک record، واژه‌ی کلیدی جدیدی را به نام with، اضافه کرده‌اند. برای نمونه اگر record زیر را در نظر بگیریم که دارای دو خاصیت نام و سن است:
public record User(string Name, int Age);
وهله سازی متداول آن به صورت زیر خواهد بود:
var user1 = new User("User 1", 21);
اما اگر خواستیم خاصیت سن آن‌را تغییر دهیم، می‌توان با استفاده از واژه‌ی کلیدی with، به صورت زیر عمل کرد:
var user2 = user1 with { Age = 31 };
کاری که در اصل در اینجا انجام می‌شود، ابتدا clone کردن شیء user1 است (یعنی دقیقا یک وهله‌ی جدید از user1 را با تمام اطلاعات قبلی آن در اختیار ما قرار می‌دهد که این وهله، ارجاعی را به شیء قبلی ندارد و از آن منقطع است). بنابراین نام user2، دقیقا همان "User 1" است که پیشتر تنظیم کردیم؛ با این تفاوت که اینبار مقدار سن آن متفاوت است. با استفاده از cloning، هنوز شیء user1 که immutable است، دست نخورده باقی مانده‌است و توسط with می‌توان خواص آن‌را تغییر داد و حاصل کار، یک شیء کاملا جدید است که مکان آن در حافظه، با مکان شیء user1 در حافظه، یکی نیست.


مقایسه‌ی نوع‌های record

در کدهای حاصل از دی‌کامپایل فوق، قسمت عمده‌ای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد می‌کنیم:
var user1 = new User("User 1", 21);
var user2 = new User("User 1", 21);
سپس یکبار آن‌ها را از طریق عملگر == و بار دیگر به کمک متد Equals، مقایسه می‌کنیم:
Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
خروجی هر دو حالت، True است:
user1.Equals(user2) -> True
user1 == user2 -> True
این مورد، یکی از مهم‌ترین تفاوت‌های recordها با classها هستند.
- زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال می‌کنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمی‌کنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
- اما اگر به قطعه کد دی‌کامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شده‌است تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجه‌ی حاصل را باز می‌گرداند:
public virtual bool Equals(User? other)
{
   return (object)other != null &&
 EqualityContract == other!.EqualityContract &&
 EqualityComparer<string>.Default.Equals(Name, other!.Name) && 
EqualityComparer<int>.Default.Equals(Age, other!.Age);
}
این متدی است که به صورت خودکار توسط کامپایلر جهت مقایسه‌ی مقادیر خواص رکورد جدید تعریف شده، تشکیل شده‌است. به عبارتی recordها از لحاظ مقایسه، شبیه به value objects عمل می‌کنند؛ هرچند در اصل یک کلاس هستند.

یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخ‌داده‌است و در نگارش‌های قبلی preview، اینگونه نبود.


امکان ارث‌بری در recordها

دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونه‌ی مشتق شده‌ی از آن، خاصیت init-only سن را نیز به همراه دارد:
    public record User
    {
        public string Name { get; init; }

        public User(string name)
        {
            Name = name;
        }
    }

    public record UserWithAge : User
    {
        public int Age { get; init; }

        public UserWithAge(string name, int age) : base(name)
        {
            Age = age;
        }
    }
در اینجا روش دیگر تعریف recordها را ملاحظه می‌کنید که شبیه به کلاس‌ها است و خواص آن init-only هستند. در این حالت اگر مقایسه‌ی زیر را انجام دهیم:
var user1 = new User("User 1");
var user2 = new UserWithAge("User 1", 21);

Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
به خروجی زیر خواهیم رسید:
user1.Equals(user2) -> False
user1 == user2 -> False
علت آن را هم پیشتر بررسی کردیم. تساوی رکوردها بر اساس مقایسه‌ی مقدار تک تک خواص آن‌ها صورت می‌گیرد و چون user1 به همراه سن نیست، مقایسه‌ی این دو، false را بر می‌گرداند.

امکان تعریف ارث‌بری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آن‌ها، مانند مثال فوق نیست:
public abstract record Food(int Calories);
public record Milk(int C, double FatPercentage) : Food(C);


رکوردها متد ToString را بازنویسی می‌کنند

در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
Console.WriteLine(user1.ToString());
Console.WriteLine(user2.ToString());
به این خروجی‌ها می‌رسیم:
User { Name = User 1 }
UserWithAge { Name = User 1, Age = 21 }
که حاصل بازنویسی خودکار متد ToString در پشت صحنه است.


امکان استفاده‌ی از Deconstruct در رکوردها

دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاس‌ها است و دیگری تعریف یک سطری، که positional record نیز نامیده می‌شود:
public record Person(string Name, int Age);
 فقط در حالت تعریف یک سطری positional record فوق است که خروجی خودکار نهایی تولیدی، به همراه public void Deconstruct نیز خواهد بود:
public void Deconstruct(out string Name, out int Age)
{
  Name = this.Name;
  Age = this.Age;
}
در این حالت می‌توان از tuples نیز برای کار با آن استفاده کرد:
var (name, age) = new Person("User 1", 21);
واژه‌ی «positional» نیز دقیقا به همین قابلیت اشاره می‌کند که بر اساس موقعیت خواص تعریف شده‌ی در رکورد، امکان Deconstruct آن‌ها به متغیرهای یک tuple وجود دارد. حالت تعریف کلاس مانند رکوردها، nominal نام دارد.


امکان استفاده‌ی از نوع‌های record در ASP.NET Core 5x

سیستم model binding در ASP.NET Core 5x، از نوع‌های record نیز پشتیبانی می‌کند؛ یک مثال:
 public record Person([Required] string Name, [Range(0, 150)] int Age);

 public class PersonController
 {
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
    // ...
   }
 }


پرسش و پاسخ

آیا نوع‌های record به صورت value type معرفی می‌شوند؟
پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل می‌کنند.

آیا می‌توان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان در رکوردها، از struct و یا کلاس‌ها جهت تعریف خواص استفاده کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان از واژه‌ی کلیدی with با کلاس‌ها و یا structها استفاده کرد؟
پاسخ: خیر. این واژه‌ی کلیدی در C# 9.0 مختص به رکوردها است.

آیا رکوردها به صورت پیش‌فرض Immutable هستند؟
پاسخ: اگر آن‌ها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شده‌ی توسط آن‌ها از نوع init-only هستند. در غیراینصورت، می‌توان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS
بازخوردها و خطاها پس از نصب :
بنده سرور مجازی 2008 رو مورد آزمایش قرار دادم :
با خطای 502.5 مواجه شدم


که طبق نکاتی که در لینک پایین تصویر وجود داشت :

Hosting bundle not installed or server not restarted

  • Browser: 502.3 Bad Gateway: There was a connection error while trying to route the request.
  • Application Log: Process ‘0’ failed to start. Port = PORT, Error Code = ‘-2147024894’.
  • ASP.NET Core Module Log: Log file created but empty 

و تغییری که در فایل webconfig جهت log کردن انجام دادم مورد زیر از توضیحات بالا مشاهده شد :
ASP.NET Core Module Log: Log file created but empty

که پس از Restart ویندوز سرور - مشکل خالی بودن فایل‌های لاگ برطرف شد و اینبار در فایل‌های لاگ با متن خطای زیر مواجه شدم:
 Failed to load the dll from [C:\Program Files\dotnet\host\fxr\1.0.1\hostfxr.dll], HRESULT: 0x80070057

در حال حاضر در صدد رفع این مشکل هستم.