The settings <appSettings> <add key="RetryAttempts" value="5" /> <add key="ApplicationBuildDate" value="11/4/1999 6:23 AM" /> </appSettings> The code dynamic config = new Configuration(); var retryAttempts1 = config.RetryAttempts; // returns 5 as a string var retryAttempts2 = config.RetryAttempts(10); // returns 5 if found in config, else 10 var retryAttempts3 = config.RetryAttempts(userInput, 10); // returns 5 if it exists in config, else userInput i
LocalDb دیتابیس توصیه شده برای ویژوال استودیو است و برای انواع پروژهها مانند اپلیکیشنهای وب میتواند استفاده شود. هنگام استفاده از این دیتابیس در IIS Express یا Cassini همه چیز طبق انتظار کار میکند. اما به محض آنکه بخواهید از آن در Full IIS استفاده کنید با خطاهایی مواجه میشوید. مقصود از Full IIS همان نسخه ای است که بهمراه ویندوز عرضه میشود و در قالب یک Windows Service اجرا میگردد.
هنگام استفاده از Full IIS دو خاصیت از LocalDb باعث بروز مشکل میشوند:
- LocalDb نیاز دارد پروفایل کاربر بارگذاری شده باشد
- بصورت پیش فرض، وهله LocalDb متعلق به یک کاربر بوده و خصوصی است
در ادامه این مقاله روی بارگذاری پروفایل کاربر تمرکز میکنیم، و در قسمت بعدی به مالکیت وهله LocalDb میپردازیم.
بارگذاری پروفایل کاربر
بگذارید دوباره به خطای موجود نگاهی بیاندازیم:
System.Data.SqlClient.SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 0 - [x89C50120])
این پیغام خطا زیاد مفید نیست، اما LocalDb اطلاعات بیشتری در Event Log ویندوز ذخیره میکند. اگر Windows Logs را باز کنید و به قسمت Application بروید پیغام زیر را مشاهده خواهید کرد.Reported at line: 400
بعلاوه این پیام خطا:
Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user.
به احتمال زیاد تعداد بیشتری از این دو خطا در تاریخچه وقایع وجود خواهد داشت، چرا که منطق کانکشن ADO.NET چند بار سعی میکند در بازههای مختلف به دیتابیس وصل شود.
پیغام خطای دوم واضح است، نیاز است پروفایل کاربر را بارگذاری کنیم. انجام اینکار زیاد مشکل نیست، هر Application Pool در IIS تنظیماتی برای بارگذاری پروفایل کاربر دارد که از قسمت Advanced Settings قابل دسترسی است. متاسفانه پس از انتشار سرویس پک 1 برای Windows 7 مسائل کمی پیچیدهتر شد. در حال حاظر فعال کردن loadUserProfile برای بارگذاری کامل پروفایل کاربر به تنهایی کافی نیست، و باید setProfileEnvironment را هم فعال کنیم. برای اطلاعات بیشتر در این باره به مستندات KB 2547655 مراجعه کنید. بدین منظور باید فایل applicationHost.config را ویرایش کنید. فایل مذکور در مسیر C:\Windows\System32\inetsrv\config قرار دارد. همانطور که در مستندات KB 2547655 توضیح داده شده، باید پرچم هر دو تنظیمات را برای ASP.NET 4.0 فعال کنیم:
<add name="ASP.NET v4.0" autoStart="true" managedRuntimeVersion="v4.0" managedPipelineMode="Integrated"> <processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="true" /> </add>
جای هیچ نگرانی نیست، چرا که این پیغام خطا انتظار میرود. همانطور که در ابتدا گفته شد، دو خاصیت LocalDb باعث بروز این خطاها میشوند و ما هنوز به خاصیت دوم نپرداخته ایم. بصورت پیش فرض وهلههای LocalDb خصوصی (private) هستند و در Windows account جاری اجرا میشوند. بنابراین ApplicationPoolIdentity در IIS به وهلههای دیتابیس دسترسی نخواهد داشت. در قسمت دوم این مقاله، راههای مختلفی را برای رفع این مشکل بررسی میکنیم.
اخیرا مایکروسافت گزارش پیشرفت امنیتی مجموعهی خودش رو از سال 2004 تا 2010 منتشر کرده که از اینجا قابل دریافت است. یکی از سؤالات مهمی که در این گزارش به صورت دو جدول ساده و زیبا پاسخ داده شده، مقایسه تمهیدات امنیتی موجود در نگارشهای مختلف ویندوز است و ... یک تصویر بهتر است از هزار گفتار.
دات نت 6 پیشنمایش 4 منتشر شد
جایگزین کردن Stack Overflow با AI
آشنایی با NHibernate - قسمت پنجم
http://blogs.imeta.co.uk/sstrong/archive/2009/12/16/824.aspx
1- مقدمه
پارتیشن بندی در بانک اطلاعاتی SQL Server، از ویژگیهایی است که از نسخه 2005، به این محصول اضافه شده است. بکارگیری این قابلیت که با Split کردن، محتوای یک جدول و قرار دادن آنها در چندین فایل، برای جداول حجیم، به ویژه جداولی که دادههای آن حاوی مقادیر تاریخچهای است، بسیار سودمند است.سادگی در مدیریت دادهها و شاخصهای موجود یک جدول (از قبیل اندازه فضای ذخیره سازی و استراتژی جدید Back up گیری)، اجرای سریعتر کوئری هایی که روی یک محدوده از دادهها کار میکنند و سهولت در آرشیو دادههای قدیمی یک جدول، از قابلیتهایی است که استفاده از این ویژگی بوجود میآورد.
محدوده استفاده از این ویژگی روی یک بانک اطلاعاتی و در یک Instance است. بنابراین مباحث مرتبط با معماری Scalability را پوشش نمیدهد و صرفاً Solution ایی است که در یک Instance بانک اطلاعاتی استفاده میشود.
2- Data File و Filegroup
هر بانک اطلاعاتی در حالت پیش فرض، شامل یک فایل دادهای (MDF.) و یک فایل ثبت تراکنشی (LDF.) میباشد. میتوان جهت ذخیره سطرهای دادهای از فایلهای بیشتری تحت نام فایلهای ثانویه (NDF.) استفاده نمود. به همان طریق که در فایل سیستم، فایلها به پوشهها تخصیص داده میشوند، میتوان Data File را به Filegroup تخصیص داد. چنانچه چندین Data File به یک Filegroup تخصیص داده شوند، دادهها در تمامی Data Fileها به طریق Round-Robin توزیع میشوند.3- Partition Function
مطابق با مقادیر تعریف شده در بدنه دستور، محدوده دادهای (پارتیشنها) با استفاده از Partition Function ایجاد میشود. با در نظر گرفتن ستونی که به عنوان Partition Key انتخاب شده، این تابع یک Data Type را به عنوان ورودی دریافت میکند. در هنگام تعریف محدوده برای پارتیشنها، به منظور مشخص کردن محدوده هر پارتیشن از Right و Left استفاده میشود.Left نمایش دهندهی حد بالای هر محدوده است و به طور مشابه، Right برای مشخص کردن حد پائین آن محدوده استفاده میشود. به منظور درک بهتر، به شکل زیر توجه نمائید:
همانطور که مشاهده میشود، همواره نیاز به یک Filegroup اضافهتری از آنچه مورد نظرتان در تعریف تابع است، میباشد. بنابراین اگر Function دارای n مقدار باشد، به n+1 مقدار برای Filegroup نیاز است.
همچنین هیچ محدودیتی برای اولین و آخرین بازه در نظر گرفته نمیشود. بنابراین جهت محدود کردن مقادیری که در این بازهها قرار میگیرند، میتوان از Check Constraint استفاده نمود.
3-1- Right or Left
یک سوال متداول اینکه از کدام مورد استفاده شود؟ در پاسخ باید گفت، به چگونگی تعریف پارتیشن هایتان وابسته است. مطابق شکل، تنها تفاوت این دو، در نقاط مرزی هر یک از پارتیشنها میباشد. در بیشتر اوقات هنگام کار با دادههای عددی میتوان از Left استفاده نمود و بطور مشابه هنگامیکه نوع دادهها از جنس زمان است، میتوان از Right استفاده کرد.
4- Partition Schema
گام بعدی پس از ایجاد Partition Function، تعریف Partition Schema است، که به منظور قرار گرفتن هر یک از پارتیشنهای تعریف شده توسط Function در Filegroupهای مناسب آن استفاده میشود.
5- Partition Table
گام پایانی ایجاد یک جدول، استفاده از Partition Schema است، که دادهها را با توجه به رویه درون Partition Function مورد استفاده، ذخیره میکند. همانطور که میدانید هنگام ایجاد یک جدول، میتوان مکان ذخیره شدن آنرا مشخص نمود.
Create Table <name> (…) ON …
در هنگام ایجاد یک جدول، معمولاً جدول در Filegroup پیش فرض که PRIMARY است، قرار میگیرد. میتوان با نوشتن نام Partition Schema و همچنین Partition Key که پیشتر ذکر آن رفت، بعد از بخش ON، برای جدول مشخص نمائیم که دادههای آن به چه ترتیبی ذخیره شوند. ارتباط این سه به شرح زیر است:
توجه شود زمانیکه یک Primary Key Constraint به یک جدول اضافه میشود، یک Unique Clustered Index نیز همراه با آن ساخته میشود. چنانچه Primary Key شامل یک Clustered Index باشد، جدول با استفاده از این ستون (ستونهای) شاخص ذخیره خواهد شد، در حالیکه اگر Primary Key شامل یک Non Clustered Index باشد، یک ساختار ذخیره-سازی اضافی ایجاد خواهد شد که دادههای جدول در آن قرار خواهند گرفت.
6- Index & Data Alignment
به عنوان یک Best Practice هنگام ایجاد یک Partition Table به منظور پارتیشن بندی، از ساختار Aligned Index استفاده شود. بدین ترتیب که تعریف Index، شامل Partition Key (ستونی که معیاری برای پارتیشن بندی است) باشد. چنانچه این عمل انجام شود، دادههای ذخیره شده مرتبط با هر پارتیشن متناظر با همان شاخص، در فایل دادهای (NDF.) ذخیره خواهند شد. از این رو چنانچه کوئری درخواست شده از جدول روی یک محدوده باشد
Where [OrderDate] Between …
بدین ترتیب برای بهرمندی از این مزایا، استفاده از Aligned Index توصیه شده است.
7- Operations
از نیازمندیهای متداول در پارتیشنینگ میتوان به افزودن، حذف پارتیشنها و جابجایی محتوای یک پارتیشن که برای عملیات آرشیو استفاده میشود، اشاره کرد.
7-1- Split Partition
به منظور ایجاد یک محدوده جدید به پارتیشنها استفاده میشود. یک نکته مهم مادامی که عملیات انتقال دادهها به پارتیشن جدید انجام میگیرد، روی جدول یک قفل انحصاری قرار میگیرد و بدین ترتیب عملیات ممکن است زمانبر باشد.
به عنوان یک Best Practice همواره یک Partition خالی را Split نمائید و پس از آن اقدام به بارگذاری داده در آن نمائید.
به یاد داشته باشید پیش از انجام عملیات splitting روی Partition Function با تغییر در Partition Schema (و بکارگیری Next Used) مشخص نمائید چه محدودهای در این Filegroup جدید قرار خواهد گرفت.
7-2- Merge Partition
به منظور ادغام پارتیشنها استفاده میشود، چنانچه پارتیشن خالی نیست، برای عملیات ادغام مسائل Performance به علت اینکه در طول عملیات از Lock (قفل انحصاری) استفاده میشود، در نظر گرفته شود.
7-3- Switch Partition
چنانچه جدول و شاخصهای آن به صورت Aligned هستند، میتوانید از Switch in و Switch out استفاده نمائید. عملیات بدین ترتیب انجام میشود که بلافاصله محتوای یک پارتیشن یا جدول (Source) در یک پارتیشن خالی جدولی دیگر و یا یک جدول خالی (Target) قرار میگیرد. عملیات تنها روی Meta Data انجام میگیرد و هیچ داده ای منتقل نمیشود.
محدودیتهای بکارگیری به شرح زیر است:
- جدول یا پارتیشن Target باید حتماً خالی باشد.
- جداول Source و Target حتماً باید در یک Filegroup یکسان قرار داشته باشند.
- جدول Source باید حاوی Aligned Indexهای مورد نیاز Target و همچنین مطابقت در Filegroup را دارا باشد.
- چنانچه Target به عنوان یک پارتیشن است، اگر Source جدول است بایست دارای یک Check Constraint باشد در غیر این صورت چنانچه یک پارتیشن است باید محدوده آن در محدوده Target قرار گیرد.
8- بررسی یک سناریوی نمونه
در ابتدا یک بانک اطلاعاتی را به طریق زیر ایجاد میکنیم:
این بانک مطابق تصویر، شامل 3 عدد فایل گروپ (FG1، FG2 و FG3) و 3 عدد دیتا فایل (P1، P2 و P3) میباشد. Filegroup پیش فرض Primary است، که چنانچه در تعریف جداول به نام Partition Schema و Partition Key مرتبط اشاره نشود، به طور پیش فرض در Filegroup موسوم به Primary قرار میگیرد. چنانچه چک باکس Default انتخاب شود، همانطور که قابل حدس زدن است، آن Filegroup در صورت مشخص نکردن نام Filegroup در تعریف جدول، به عنوان مکان ذخیره سازی انتخاب میشود. چک باکس Read Only نیز همانطور که از نامش پیداست، چنانچه روی یک Filegroup تنظیم گردد، عملیات مربوط به Write روی دادههای آن قابل انجام نیست و برای Filegroup هایی که جنبه نگهداری آرشیو را دارند، قابل استفاده است.
چنانچه Filegroup ای را از حالت Read Only دوباره خارج کنیم، میتوان عملیات Write را دوباره برای آن انجام داد.
پس از ایجاد بانک اطلاعاتی، گام بعدی ایجاد یک Partition Function و پس از آن یک Partition Schema است. همانطور که مشاهده میکنید در Partition Function از سه مقدار استفاده شده، بنابراین در Partition Schema باید از چهار Filegroup استفاده شود، که در مثال ما از Filegroup پیش فرض که Primary است، استفاده شده است.
USE [PartitionDB] GO CREATE PARTITION FUNCTION pfOrderDateRange(DATETIME) AS RANGE LEFT FOR VALUES ('2010/12/31','2011/12/31','2012/12/31') GO CREATE PARTITION SCHEME psOrderDateRange AS PARTITION pfOrderDateRange TO (FG1,FG2,FG3,[PRIMARY]) GO
پس از طی گامهای قبل، به ایجاد یک جدول به صورت Aligned Index مبادرت ورزیده میشود.
CREATE TABLE Orders ( OrderID INT IDENTITY(1,1) NOT NULL, OrderDate DATETIME NOT NULL, OrderFreight MONEY NULL, ProductID INT NULL, CONSTRAINT PK_Orders PRIMARY KEY CLUSTERED (OrderID ASC, OrderDate ASC) ON psOrderDateRange (OrderDate) ) ON psOrderDateRange (OrderDate) GO
در ادامه برای بررسی درج اطلاعات در پارتیشن با توجه به محدوده آنها اقدام به افزودن رکوردهایی در جدول ساخته شده مینمائیم.
SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2010/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2011/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2012/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO
از طریق دستور Select زیر میتوان نحوه توزیع دادهها را در جدول مشاهده کرد.
USE [PartitionDB] GO SELECT OBJECT_NAME(i.object_id) AS OBJECT_NAME, p.partition_number, fg.NAME AS FILEGROUP_NAME, ROWS, au.total_pages, CASE boundary_value_on_right WHEN 1 THEN 'Less than' ELSE 'Less or equal than' END AS 'Comparition',VALUE FROM sys.partitions p JOIN sys.indexes i ON p.object_id = i.object_id AND p.index_id = i.index_id JOIN sys.partition_schemes ps ON ps.data_space_id = i.data_space_id JOIN sys.partition_functions f ON f.function_id = ps.function_id LEFT JOIN sys.partition_range_values rv ON f.function_id = rv.function_id AND p.partition_number = rv.boundary_id JOIN sys.destination_data_spaces dds ON dds.partition_scheme_id = ps.data_space_id AND dds.destination_id = p.partition_number JOIN sys.filegroups fg ON dds.data_space_id = fg.data_space_id JOIN (SELECT container_id, SUM(total_pages) AS total_pages FROM sys.allocation_units GROUP BY container_id) AS au ON au.container_id = p.partition_id WHERE i.index_id < 2
خروجی دستور فوق به شرح زیر است:
در ادامه به ایجاد یک Filegroup جدید میپردازیم.
/* Query 2-3- Split a partition*/ -- Add FG4: ALTER DATABASE PartitionDB ADD FILEGROUP FG4 Go ALTER PARTITION SCHEME [psOrderDateRange] NEXT USED FG4 GO ALTER PARTITION FUNCTION [pfOrderDateRange]() SPLIT RANGE('2013/12/31') GO -- Add Partition 4 (P4) to FG4: GO ALTER DATABASE PartitionDB ADD FILE ( NAME = P4, FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\P4.NDF' , SIZE = 1024KB , MAXSIZE = UNLIMITED, FILEGROWTH = 10%) TO FILEGROUP [FG4] -- GO
و در ادامه به درج اطلاعاتی برای بررسی نحوه توزیع دادهها در Filegroup هایمان میپردازیم.
SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2013/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2012/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO
جهت ادغام پارتیشنها به طریق زیر عمل میشود:
/* Query 2-4- Merge Partitions */ ALTER PARTITION FUNCTION [pfOrderDateRange]() MERGE RANGE('2010/12/31') Go
به منظور آرشیو نمودن اطلاعات به طریق زیر از Switch استفاده میکنیم. ابتدا یک جدول موقتی برای ذخیره رکوردهایی که قصد آرشیو آنها را داریم، ایجاد میکنیم. همانگونه که در تعریف جدول مشاهده میکنید، نام Filegroup ای که برای ساخت این جدول استفاده میشود، با Filegroup ای که قصد آرشیو اطلاعات آنرا داریم، یکسان است.
در ادامه میتوان مثلاً با ایجاد یک Temporary Table به انتقال این اطلاعات بدون توجه به Filegroup آنها پرداخت.
/* Query 2-5- Switch Partitions */ USE [PartitionDB] GO CREATE TABLE [dbo].[Orders_Temp]( [OrderID] [int] IDENTITY(1,1) NOT NULL, [OrderDate] [datetime] NOT NULL, [OrderFreight] [money] NULL, [ProductID] [int] NULL, CONSTRAINT [PK_OrdersTemp] PRIMARY KEY CLUSTERED ([OrderID] ASC,[OrderDate] ASC)ON FG2 ) ON FG2 GO USE [tempdb] GO CREATE TABLE [dbo].[Orders_Hist]( [OrderID] [int] NOT NULL, [OrderDate] [datetime] NOT NULL, [OrderFreight] [money] NULL, [ProductID] [int] NULL, CONSTRAINT [PK_OrdersTemp] PRIMARY KEY CLUSTERED ([OrderID] ASC,[OrderDate] ASC) ) GO USE [PartitionDB] GO ALTER TABLE [dbo].[Orders] SWITCH PARTITION 1 TO [dbo].[Orders_Temp] GO INSERT INTO [tempdb].[dbo].[Orders_Hist] SELECT * FROM [dbo].[Orders_Temp] GO DROP TABLE [dbo].[Orders_Temp] GO SELECT * FROM [tempdb].[dbo].[Orders_Hist]
هنگام ایجاد یک Type جدید باید سعی کنیم دیتای داخلی Type را تا حد ممکن Immutable کنیم. حتی اگر نیاز داریم یک شیء را برگردانیم، بهتر است که یک instance جدید را برگردانیم، نه اینکه همان شیء موجود را تغییر دهیم. نتیحه این کار نهایتا به شفافیت بیشتر و Thread-Safe بودن منجر خواهد شد.
مثال:
public class Rectangle { public int Length { get; set; } public int Height { get; set; } public void Grow(int length, int height) { Length += length; Height += height; } } Rectangle r = new Rectangle(); r.Length = 5; r.Height = 10; r.Grow(10, 10);// r.Length is 15, r.Height is 20, same instance of r
// After public class ImmutableRectangle { int Length { get; } int Height { get; } public ImmutableRectangle(int length, int height) { Length = length; Height = height; } public ImmutableRectangle Grow(int length, int height) => new ImmutableRectangle(Length + length, Height + height); } ImmutableRectangle r = new ImmutableRectangle(5, 10); r = r.Grow(10, 10);// r.Length is 15, r.Height is 20, is a new instance of r
یکی از موارد با اهمیت در سبک کد نویسی تابعی را در مثال زیر ببینید:
public static void Main() { Console.WriteLine(GetSalutation(DateTime.Now.Hour)); } // imparitive, mutates state to produce a result /*public static string GetSalutation(int hour) { string salutation; // placeholder value if (hour < 12) salutation = "Good Morning"; else salutation = "Good Afternoon"; return salutation; // return mutated variable }*/ public static string GetSalutation(int hour) => hour < 12 ? "Good Morning" : "Good Afternoon";
در قسمت قبلی درباره توابع HOF صحبت کردیم. به طور خلاصه توابعی که یک تابع را به عنوان ورودی میگیرند و یک تابع را به عنوان خروجی برمیگردانند. به مثال زیر توجه کنید:
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { int count = 0; foreach (TSource element in source) { checked { if (predicate(element)) { count++; } } } return count; }
ترکیب توابع به عمل پیوند دادن چند تابع ساده، برای ایجاد توابعی پیچیده گفته میشود. دقیقا مانند عملی که در ریاضیات انجام میشود. خروجی هر تابع به عنوان ورودی تابع بعدی مورد استفاده قرار میگیرد و در آخر ما خروجی آخرین فراخوانی را به عنوان نتیجه دریافت میکنیم. ما میتوانیم در #C به روش برنامه نویسی تابعی، توابع را با یکدیگر ترکیب کنیم. به مثال زیر توجه کنید:
public static class Extensions { public static Func<T, TReturn2> Compose<T, TReturn1, TReturn2>(this Func<TReturn1, TReturn2> func1, Func<T, TReturn1> func2) { return x => func1(func2(x)); } } public class Program { public static void Main(string[] args) { Func<int, int> square = (x) => x * x; Func<int, int> negate = x => x * -1; Func<int, string> toString = s => s.ToString(); Func<int, string> squareNegateThenToString = toString.Compose(negate).Compose(square); Console.WriteLine(squareNegateThenToString(2)); } }
یکی از روشهای مهم در سبک برنامه نویسی تابعی، فراخوانی متدها به صورت زنجیرهای و پاس دادن خروجی یک متد به متد بعدی، به عنوان ورودی است. به عنوان مثال کلاس String Builder یک مثال خوب از این نوع پیاده سازی است. کلاس StringBuilder از پترن Fluent Builder استفاده میکند. ما میتوانیم با اکستنشن متد هم به همین نتیجه برسیم. نکته مهم در مورد کلاس StringBuilder این است که این کلاس، شیء string را mutate نمیکند؛ به این معنا که هر متد، تغییری در object ورودی نمیدهد و یک خروجی جدید را بر میگرداند.
string str = new StringBuilder() .Append("Hello ") .Append("World ") .ToString() .TrimEnd() .ToUpper();
public static class Extensions { public static StringBuilder AppendWhen(this StringBuilder sb, string value, bool predicate) => predicate ? sb.Append(value) : sb; } public class Program { public static void Main(string[] args) { // Extends the StringBuilder class to accept a predicate string htmlButton = new StringBuilder().Append("<button").AppendWhen(" disabled", false).Append(">Click me</button>").ToString(); } }
نوعهای اضافی درست نکنید ، به جای آن از کلمهی کلیدی yield استفاده کنید!
گاهی ما نیاز داریم لیستی از آیتمها را به عنوان خروجی یک متد برگردانیم. اولین انتخاب معمولا ایجاد یک شیء از جنس List یا به طور کلیتر Collection و سپس استفاده از آن به عنوان نوع خروجی است:
public static void Main() { int[] a = { 1, 2, 3, 4, 5 }; foreach (int n in GreaterThan(a, 3)) { Console.WriteLine(n); } } /*public static IEnumerable<int> GreaterThan(int[] arr, int gt) { List<int> temp = new List<int>(); foreach (int n in arr) { if (n > gt) temp.Add(n); } return temp; }*/ public static IEnumerable<int> GreaterThan(int[] arr, int gt) { foreach (int n in arr) { if (n > gt) yield return n; } }
در قسمت قبلی به طور کلی درباره برنامه نویسی Imperative صحبت کردیم. در مثال زیر یک نمونه از تبدیل یک متد که با استایل Imperative نوشته شده به declarative را میبینید. شما میتوانید ببینید که چقدر کوتاهتر و خواناتر شده:
List<int> collection = new List<int> { 1, 2, 3, 4, 5 }; // Imparative style of programming is verbose List<int> results = new List<int>(); foreach(var num in collection) { if (num % 2 != 0) results.Add(num); } // Declarative is terse and beautiful var results = collection.Where(num => num % 2 != 0);
Immutable Collection
در مورد اهمیت immutable قبلا صحبت کردیم؛ Immutable Collection ها، کالکشنهایی هستند که به جز زمانیکه ایجاد میشنود، اعضای آنها نمیتوانند تغییر کنند. زمانیکه یک آیتم به آن اضافه یا کم شود، یک لیست جدید، برگردانده خواهد شد. شما میتوانید انواع این کالکشنها را در این لینک ببینید.
به نظر میرسد که ایجاد یک کالکشن جدید میتواند سربار اضافی بر روی استفاده از حافظه داشته باشد، اما همیشه الزاما به این صورت نیست. به طور مثال اگر شما f(x)=y را داشته باشید، مقادیر x و y به احتمال زیاد یکسان هستند. در این صورت متغیر x و y، حافظه را به صورت مشترک استفاده میکنند. به این دلیل که هیچ کدام از آنها Mutable نیستند. اگر به دنبال جزییات بیشتری هستید این مقاله به صورت خیلی جزییتر در مورد نحوه پیاده سازی این نوع کالکشنها صحبت میکند. اریک لپرت یک سری مقاله در مورد Immutable ها در #C دارد که میتوانید آن هار در اینجا پیدا کنید.
Thread-Safe Collections
Collection | توضیحات |
ConcurrentDictionary | پیاده سازی thread safe از دیکشنری key-value |
ConcurrentQueue | پیاده سازی thread safe از صف (اولین ورودی ، اولین خروجی) |
ConcurrentStack | پیاده سازی thread safe از پشته (آخرین ورودی ، اولین خروجی) |
ConcurrentBag | پیاده سازی thread safe از لیست نامرتب |
این کلاسها در واقع همه مشکلات ما را حل نخواهند کرد؛ اما بهتر است که در ذهن خود داشته باشیم که بتوانیم به موقع و در جای درست از آنها استفاده کنیم.
در این قسمت از مقاله سعی شد با روشهای خیلی ساده، با مفاهیم اولیه برنامه نویسی تابعی درگیر شویم. در ادامه مثالهای بیشتری از الگوهایی که میتوانند به ما کمک کنند، خواهیم داشت.