مطالب
مشکل همزمانی خواندن و به روز رسانی اطلاعات در برنامه‌های وب
فرض کنید در برنامه‌ی خود «کیف پولی» را طراحی کرده‌اید که بر اساس آن، کاربر می‌تواند خرید کند. این کیف پول، از Id کاربر و موجودی فعلی او تشکیل می‌شود:
CREATE TABLE accounts (
user_id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
);
و برای مثال موجودی فعلی کاربر 1، مقدار 300 است:
INSERT INTO accounts(user_id, balance)
VALUES (1, 300);
اکنون کوئری‌های متداول زیر را که از یک read و سپس update تشکیل شده‌اند، درنظر بگیرید:
DECLARE @amount INT;

SET @amount = (
SELECT balance
FROM accounts
WHERE user_id = 1
);

SELECT @amount as 'balance'

UPDATE accounts
SET balance =  @amount - 100
WHERE user_id = 1;

SELECT balance as 'balance after shopping'
FROM accounts
WHERE user_id = 1
- دو عمل read و سپس update صورت گرفته‌ی فوق، مربوط به یک درخواست خرید است.
- در اینجا مقدار متغیر amount در ابتدای کار، مساوی 300 است که مربوط به همان insert ابتدایی است.
- سپس از این مقدار در کوئری دومی (برای مثال حاصل از خرید شماره یک)، 100 واحد کم می‌شود (برای مثال قیمت کل خرید است).
- در این حالت نتیجه‌ی آن یا همان موجودی جدید کاربر، 200 خواهد بود.

معادل این عملیات در EF-Core چنین دستورات متداولی است:
var account1 =  context.Accounts.First(x => x.UserId == 1);
account1.Balance -= 100;
context.SaveChanges();

سؤال: اگر کوئری‌های فوق را در یک برنامه‌ی ذاتا چند ریسمانی وب، دوبار به صورت همزمان اجرا کنیم، یعنی دو عمل خرید موازی را شبیه سازی کنیم، چه اتفاقی رخ می‌دهد؟ آیا موجودی نهایی اینبار برای مثال 100 می‌شود (با فرض 300 بودن موجودی ابتدایی)؟
پاسخ خیر است! و آن‌را می‌توانید در تصویر زیر مشاهده کنید:



در اینجا برای شبیه سازی اجرای موازی دو کوئری، از دستور WAITFOR TIME استفاده شده‌است که برای برای آزمایش آن می‌توانید مقدار آن‌را به یک دقیقه بعد تنظیم کرده و سپس آن‌را در دو پنجره‌ی SQL server management studio اجرا کنید.
همانطور که مشاهده می‌کنید، با اجرای موازی این دو کوئری، یعنی دوبار خرید کردن همزمان، 100 واحد گم شده‌است ! به این مشکل همزمانی read و سپس update رخ داده، یک «race condition» گفته می‌شود و این روزها که مطالب منتشر شده‌ی از آسیب پذیری‌های برنامه‌های وب ایرانی را بررسی می‌کنم، این مورد در صدر آن‌ها قرار دارد!
علت اینجا است که عموما برنامه نویس‌ها، برنامه‌های وب را در یک تک سشن باز شده‌ی توسط مرورگر خود آزمایش می‌کنند و در این حالت، همه چیز خوب است و اعمال آن به ترتیب پیش می‌روند. اما فراموش می‌کنند که می‌توان قسمت‌های مختلف برنامه‌های وب را به صورت همزمان، موازی و چندباره نیز اجرا کرد؛ حتی اگر آن قسمت متعلق به یک کاربر باشد.


سؤال: آیا استفاده تراکنش‌ها این مشکل را حل نمی‌کنند؟!

عموما برنامه نویس‌ها تصور می‌کنند که می‌توانند تمام اینگونه مشکلات را با تراکنش‌ها حل کنند:



همانطور که مشاهده می‌کنید، اینبار هرچند هر دو عملیات خرید داخل BEGIN TRAN و COMMIT TRAN قرار گرفته‌اند، اما ... مشکل همزمانی هنوز پابرجا است! چون نوع پیش‌فرض تراکنش مورد استفاده، READ COMMITTED isolation level است و عدم دقت به آن ممکن است این تصور را ایجاد کند که با تعریف تراکنش‌ها، تمام مشکلات همزمانی برطرف می‌شوند.


راه‌حل‌های پیشنهادی جهت حل مشکل همزمانی عملیات read/update

برای حل مشکلات مرتبط با race condition و همزمانی درخواست‌های read/update، می‌توان از یکی از روش‌های زیر استفاده کرد:
الف) بجای اینکه یکبار کوئری read و یکبار کوئری update به صورت جداگانه صادر شوند، فقط یکبار کوئری update داشته باشیم.
ب) پیاده سازی Row level locking؛ در صورت پشتیبانی بانک اطلاعاتی مورد استفاده از آن
ج) استفاده از تراکنش‌هایی از نوع SERIALIZABLE
د) پیاده سازی optimistic locking

این موارد را در ادامه با توضیحات بیشتری بررسی می‌کنیم.


الف) پرهیز از خواندن و به روز رسانی جداگانه

بجای اینکه مانند اعمال فوق، یکبار select داشته باشیم و یکبار  update، بهتر است فقط یک دستور update بکارگرفته شود:
UPDATE accounts
SET balance =  balance - 100
WHERE user_id = 1;


اینبار با خلاصه شدن دو دستور select و update به یک دستور update، دیگر پس از دو خرید همزمان، 100 واحد گم شده مشاهده نمی‌شود (!) و موجودی نهایی صحیح است.


ب) پیاده سازی Row level locking

همیشه امکان تغییر عملیات مورد نیاز، به سادگی حالت الف نیست. در یک چنین حالت‌هایی جهت حداقل شدن تغییرات مورد نیاز، می‌توان از row level locking استفاده کرد:
WAITFOR TIME '13:47:00';

SET NOCOUNT, XACT_ABORT ON;

BEGIN TRAN;

DECLARE @amount INT;

SET @amount = (
 SELECT balance
 FROM accounts WITH (UPDLOCK, HOLDLOCK)
 WHERE user_id = 1
 );

SELECT @amount as 'initial user''s balance'

UPDATE accounts
SET balance =  @amount - 100
WHERE user_id = 1;

SELECT balance as 'user''s balance after shopping 1'
FROM accounts
WHERE user_id = 1;

COMMIT TRAN;



در اینجا اضافه شدن WITH (UPDLOCK, HOLDLOCK) را به Select تعریف شده، مشاهده می‌کنید که به آن‌ها locking hints هم گفته می‌شود و داخل BEGIN TRAN و COMMIT TRAN عمل می‌کنند (که نوع پیش‌فرض آن READ COMMITTED isolation level است). کار UPDLOCK، تبدیل shared lock پیش‌فرض، به update lock است و کار HOLDLOCK، نگه داشتن قفل صورت گرفته تا پایان کار تراکنش تعریف شده‌است.
با این تغییرات، هر تراکنش همزمان دیگری، تا زمانیکه قفل صورت گرفته‌ی بر روی ردیف select، رها نشود (یعنی تا زمانیکه تراکنش قفل کننده، به COMMIT TRAN برسد)، نمی‌تواند آن‌را تغییر دهد. به همین جهت است که در تصویر فوق، هرچند هر دو عملیات همزمان اجرا شده‌اند، اما یکی موجودی ابتدایی 300 را می‌بیند و دیگری پس از صبر کردن تا پایان تراکنش و رها شدن قفل، موجودی تغییر یافته‌ی جدیدی را مشاهده کرده و از آن استفاده می‌کند. به این ترتیب دیگر 100 واحدی که در اولین تصویر این مطلب مشاهده کردید، گم نشده‌است.


ج) استفاده از تراکنش‌هایی از نوع SERIALIZABLE

بجای استفاده از روش row level locking یاد شده، روش دیگری را که می‌توان استفاده کرد، تغییر نوع پیش‌فرض تراکنش مورد استفاده‌است. برای مثال اگر از یک SERIALIZABLE transaction استفاده کنیم؛ یعنی SET TRANSACTION ISOLATION LEVEL SERIALIZABLE  را در ابتدای کار ذکر کنیم و برای مثال دو تراکنش همزمان را اجرا کنیم، اگر در تراکنش اول اطلاعاتی خوانده شود، در هیچ تراکنش دیگری نمی‌توان این اطلاعات خوانده شده را تا پایان کار تراکنش اول، تغییر داد:




د) پیاده سازی optimistic locking

پیاده سازی optimistic locking و یا Optimistic concurrency control عموما در سمت برنامه رخ می‌دهد و توسط ORMها زیاد مورد استفاده قرار می‌گیرد؛ مانند اضافه کردن ستون اضافی version و یا timestamp به جداول تعریف شده. در این حالت تمام updateها به همراه یک where اضافی هستند تا بررسی کنند که آیا version دریافتی در حین خواندن ردیف در حال به روز رسانی، تغییر کرده‌است یا خیر؟ اگر تغییر کرده‌است، تراکنش را با خطایی خاتمه خواهند داد. این روش برخلاف حالت‌های ب و ج، حتی خارج از یک تراکنش نیز کار می‌کند و مشکلات قفل کردن طولانی مدت رکوردها توسط آن‌ها را به همراه ندارد.
اشتراک‌ها
سایت فارسی dotnetnke
پلت‌فرم دی‌ان‌ان که قبلا با نام دات‌نت‌نیوک شناخته می‌شد، یک سیستم مدیریت محتوای کدباز، قابل توسعه، امن و مقیاس پذیر است که صدها هزار کسب و کار کوچک و بزرگ از آن برای راه اندازی وب سایت خود استفاده کرده‌اند. پلت‌فرم دی‌ان‌ان در حقیقت نسخه رایگان دی‌ان‌ان و همچنین بستر و مبنای نسخه‌های تجاری دی‌ان‌ان نظیر ایوک کانتنت و ایوک اِنگیج می‌باشد. نسخه‌های تجاری دی‌ان‌ان شامل پلت‌فرم دی‌ان‌ان و چندین افزونه جدید و مکمل می‌باشند. دی‌ان‌ان با داشتن بیش از یک میلیون عضو، بیش از یک میلیون دانلود و بیش از هشت صد هزار سایت مبتنی بر آن در طول ده سال گذشته، ثابت کرده است که بستری قابل اعتماد بوده و شما می‌توانید با اطمینان کسب و کار خود را با تکیه بر آن بسازید .
سایت فارسی dotnetnke
اشتراک‌ها
دانلود سورس باز نسخه 3.10 Nop Commerce
نرم افزار Nop Commerce
بهترین فروشگاه رایگان! nopCommerce بصورت منبع باز است. یک پروژه با ویژگی‌های جامع است که به سادگی برای هرنوع کسب و کار جدید کارایی دارد ، در عین حال به اندازه کافی قدرتمند است. این یک پلت فرم تجارت الکترونیک امن، مقیاس پذیر و قابل تمدید است.   بسیاری از تکنیک‌های معماری بصورت کامل در این پروژه دیده میشود.  توضیحات:
C# MVC 4.5 (4) - Code First - DDD - Multilayered software architecture 
دانلود سورس باز نسخه 3.10 Nop Commerce
نظرات مطالب
شروع کار با ASP.NET Web API 2
بله. به طور کلی، هر پلتفرمی که دارای کتابخانه ای جهت کار با سرویس‌های Http است می‌تواند از سرویس‌های Asp.Net WebApi استفاده نماید.
 اما در هنگام پیاده سازی پروژه‌های مقیاس بزرگ حتما به طراحی زیر ساخت توجه ویژه ای داشته باشید. اگر کتاب‌های
 Designing Evolvable Web Api With Asp.Net یا
Pro Asp.Net Web Api : Http Web Service In Asp.Net   را مطالعه نکردید بهتون پیشنهاد می‌کنم قبل از شروع به کار حتما نگاهی به آن‌ها بیندازید.

در همین رابطه:
»مقایسه بین امکانات Web Api و WCF
نظرات مطالب
آموزش TypeScript #1
بله امکان پذیر است. اما با توجه به این نکته که فلسفه وجودی TypeScript این است که در پروژه هایی با مقیاس پزرگ برای سازمان دهی کدهای سمت کلاینت مورد استفاده قرار گیرند و یکی از روش‌های سازمان دهی کدها این است که کد‌های TypeScript در فایل هایی جداگانه با پسوند ts ذخیره شده تا کامپایل و تبدیل به کد JavaScript شوند(مهم‌ترین مزیت این روش این است که از نوشتن کد‌های تکراری جلوگیری می‌شود). اما در صورتی که مایل به نوشتن کد به صورت Embed در تگ Script هستید باید از پروژه‌های متن بازی همچون TypeScript Compile یا ts-htaccess  استفاده کنید.
نظرات نظرسنجی‌ها
از چند مانیتور برای برنامه نویسی استفاده میکنید؟
اگر اندازه مانیتور و رزولوشن یکسان باشه بهتره، من از دو ماینتور 20 و 22 اینچ سامسونگ و ال جی استفاده می‌کنیم، کیفیت رنگ سامسونگ بهتره و این تفاوت کیفیت کمی آزاردهنده اس، هم چنین ماینتور بزرگتر رزولوشن بالاتری داره و سایز تسکت دو طرف با هم اختلاف مقیاس داره و اون هم کمی نا هماهنگی ایجاد میکنه
هم چنین نرم افزارهای مدیریت dual monitor رو حتما امتحان کنید ، داشتن دو تسک بار مجزا و مستقل از هم برای هر مانیتور و انتقال یک پنجره به مانیتور دیگه با یک کلیک خیلی تجربه استفاده خوبی داره.
مطالب
Best Practice هایی برای ASP.NET MVC - قسمت اول
در سایت جاری مطالب زیادی درباره ASP.NET MVC نوشته شده است. این مطلب و قسمت بعدی آن مروری خواهد داشت بر Best Practice‌ها در ASP.NET MVC.

استفاده از NuGet Package Manager برای مدیریت وابستگی‌ها

درباره اهمیت NuGet برای مصرف کنندگان قبلا این مطلب نوشته شده است.
بجای صرف وقت برای اینکه بررسی کنیم آیا این نسخه‌ی جدید کتابخانه‌ی X یا اسکریپت jQuery آمده است یا خیر، می‌توان این وظیفه را به NuGet سپرد. علاوه بر این NuGet مزیت‌های دیگری هم دارد؛ مثلا تیم‌های برنامه نویسی می‌توانند کتاب خانه‌های مشترک خودشان را در مخزن‌های سفارشی NuGet قرار دهند و توزیع و Versioning آن‌را به NuGet بسپارند.


تکیه بر Abstraction (انتزاع)

Abstraction در طراحی سیستم‌ها منجر به تولید نرم افزار هایی Loosely coupled با قابلیت نگهداری بالا و همچنین فراهم شدن زمینه برای نوشتن Unit Test می‌شود.
اگر به مطالب قبلی وب سایت برگردیم در مطلب چرا ASP.NET MVC گفته شد که :
2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک :
در طراحی ASP.NET MVC همه‌جا interface‌ها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویس‌های مستقل برای ASP.NET MVC طراحی شده‌اند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمی‌آید؟
عوضش کنید! سیستم اعتبار سنجی توکار آن‌را دوست ندارید؟ آن‌را با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interface‌ها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی
mocking (تقلید) آن‌ها است جهت ساده سازی نوشتن آزمون‌های واحد.


از کلمه‌ی کلیدی New استفاده نکنید

هر جا ممکن است کار وهله سازی اشیاء را به لایه و حتی Framework دیگری بسپارید. هر زمان اشیاء نرم افزار خودتان را با کلمه‌ی new وهله سازی می‌کنید اصل Abstraction را فراموش کرده اید. هر زمان اشیاء نرم افزار را مستقیم وهله سازی می‌کنید در نظر داشته باشید می‌توانید آنها را به صورت وابستگی تزریق کنید.
در همین رابطه مطالب زیر را از دست ندهید :


از HttpContext مستقیما استفاده نکنید (از HttpContextBase استفاده کنید)

از .NET 4 به بعد فضای نامی تعریف شده که در بر دارنده‌ی کلاس‌های انتزاعی (Abstraction) خیلی از قسمت‌های اصلی ASP.NET است.  یکی از مواردی که در توسعه‌ی ASP.NET معمولا زیاد استفاده می‌شود، شیء HttpContext است . استفاده از HttpContextBase را به استفاده از HttpContext ترجیح دهید. اهمیت این موضوع در راستای اهمیت انتزاع (Abstraction) می‌باشد.


از "رشته‌های جادویی" اجتناب کنید

استفاده از رشته‌های جادویی در خیلی از جاها کار را ساده می‌کند؛ بعضی وقت‌ها هم به آنها نیاز است اما مشکلات زیادی دارند :
  1. رشته‌ها معنای باطنی ندارند (مثلا : دشوار است که از روی نام یک ID مشخص کنم این ID چطور به ID دیگری مرتبط است و یا اصلا ربط دارد یا خیر)
  2. با اشتباهات املایی یا عدم رعایت حروف بزرگ و کوچک ایجاد مشکل می‌کنند.
  3. به Refactoring واکنش خوبی نشان نمی‌دهند. (برای درک بهتر این مطلب را بخوانید.)
برای درک بهتر 2، یک مثال بررسی می‌شود؛ اولی از رشته‌های جادویی برای دسترسی به داده در ViewData استفاده می‌کند و دومی refactor شده‌ی مثال اول است که از strongly type مدل برای دسترسی به همان داده استفاده می‌کند.
<p>
    <label for="FirstName">First Name:</label>
    <span id="FirstName">@ViewData["FirstName"]</span>
</p>
مثال دوم :
<p>
    <label for="FirstName">First Name:</label>
    <span id="FirstName">@Model.FirstName</span>
</p>
مثلا مثال دوم مشکلات رشته‌های جادویی را ندارد.
در رابطه با Magic strings این مطلب را مطالعه بفرمایید.


از نوشتن HTML در کدهای "Backend" خودداری کنید

با توجه به اصل جداسازی وابستگی‌ها (Separation of Concerns) وظیفه‌ی کنترلر‌ها و دیگر کدهای backend رندر کردن HTML نیست. (ساده سازی کنترلر ها) البته در نظر داشته باشید که قطعا تولید HTML در متد‌های کمکی کلاس هایی که "تنها" وظیفه‌ی آنها کمک به View‌ها جهت تولید کد هست ایرادی ندارد. این کلاس‌ها بخشی از View در نظر گرفته می‌شوند نه کدهای "backend".


در View‌ها "Business logic" انجام ندهید

معکوس بند قبلی هم کاملا صدق می‌کند ، منظور این است که View‌ها تا جایی که ممکن است باید حاوی کمترین Business logic ممکن باشند. در واقع تمرکز View‌ها باید استفاده و نحوه‌ی نمایش داده ای که برای آن‌ها فراهم شده باشد نه انجام عملیات روی آن.


استفاده از Presentation Model را به استفاده مستقیم از Business Object‌ها ترجیح دهید

در مطالب مختلف وب سایت اشاره به اهمین ViewModel‌ها شده است. برای اطلاعات بیشتر بند ج آموزش 11 از سری آموزش‌های ASP.NET MVC را مطالعه بفرمایید.


If‌های شرطی را در View‌ها را در متد‌های کمکی کپسوله کنید

استفاده از شرط‌ها در View کار توسعه دهنده را برای یک سری اعمال ساده می‌کند اما می‌تواند باعث کمی کثیف کاری هم شود. مثلا:
@if(Model.IsAnonymousUser) {
    <img src="@Url.Content("~/content/images/anonymous.jpg")" />
} else if(Model.IsAdministrator) {
    <img src="@Url.Content("~/content/images/administrator.jpg")" />
} else if(Model.Membership == Membership.Standard) {
    <img src="@Url.Content("~/content/images/member.jpg")" />
} else if(Model.Membership == Membership.Preferred) {
    <img src="@Url.Content("~/content/images/preferred_member.jpg")" />
}
می‌توان این کد که تا حدودی شامل منطق تجاری هم هست را در یک متد کمکی کپسوله کرد :
public static string UserAvatar(this HtmlHelper<User> helper)
{
    var user = helper.ViewData.Model;
    string avatarFilename = "anonymous.jpg";
    if (user.IsAnonymousUser)
    {
        avatarFilename = "anonymous.jpg";
    }
    else if (user.IsAdministrator)
    {
        avatarFilename = "administrator.jpg";
    }
    else if (user.Membership == Membership.Standard)
    {
        avatarFilename = "member.jpg";
    }
    else if (user.Membership == Membership.Preferred)
    {
        avatarFilename = "preferred_member.jpg";
    }
    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
    var contentPath = string.Format("~/content/images/{0}", avatarFilename)
    string imageUrl = urlHelper.Content(contentPath);
    return string.Format("<img src='{0}' />", imageUrl);
}
اکنون برای نمایش آواتار کاربر به سادگی می‌توان  نوشت :
@Html.UserAvatar()
به این ترتیب کد ما تمیز‌تر شده ، قابلیت نگهداری آن بالاتر رفته ، منطق تجاری یک بار و در یک قسمت نوشته شده از این کد در جاهای مختلف سایت می‌توان استفاده کرد و اگر لازم به تغییر باشد با تغییر در یک قسمت همه جا اعمال می‌شود.

منتظر قسمت بعدی باشید.
 
نظرات مطالب
آشنایی با الگوی Adapter
با سلام و تشکر؛  اینکه شما این جمله را به کار بردید:
"کلاس Adapter  جهت تبادل اطلاعات بین ITarget و Adaptee هر دو را در خود Implement می‌نماید، به عبارتی از هر دو مشتق (Inherit) می‌شود."
تفاوت بسیاری بین Inherit و Implement وجود دارد و اصلا کلاسی نمی‌تواند از اینترفیس مشتق شود. درست است که بگوییم: Adapter از کلاس Adaptee برای فراخوانی متدهای ان ارث(Inherit) میبرد و ITarget را برای ایجاد یک رابط برای کاربر پیاده سازی(Implement) میکند.
پاسخ به بازخورد‌های پروژه‌ها
درخواست ایده برای برای پیاده سازی منوی چند سطحی
آقای محسن خان تشکر بابت پاسخ گویی سریع .
لینکی که شما معرفی کردید با خواسته من متفاوت است . به طور مثال گوشی LG V10 می‌تواند در گروه‌های زیر قرار گیرد :
1-بر اساس سیستم عامل ( اندروید)
2-شبکه ارتباطی : 4G
3- تعداد سیم کارت : یک
و الی آخر
ولی در لینکی که شما قرار داید "درب آهنی " نمی‌تواند در زیر شاخه " سقف" قرار گیرد .
 
مطالب
بررسی مباحث Streaming در ‎‎‎.NET - مقدمه

هدف بررسی کامل مباحث Streaming در دات نت فریمورک می‌باشد.

Stream چیست؟

دنباله‌ای از بایت‌ها که می‌توان آنها را از یک backing store (انبار پشتیبان) خواند یا در آن نوشت.

Backing Store 

یک رسانه ذخیره سازی از جمله Disk-Drive، Memory و Network Location می‌باشد که به عنوان منبع یا مقصدی برای خواندن و نوشتن بایت‌ها به صورت دنباله‌ای، می‌توان از آن استفاده کرد.


زمانی که قرار است داده ذخیره شده به صورت Stream مصرف شود، مزیت مقیاس پذیری را نیز خواهید داشت. لذا لازم نیست با مشکل محدودیت حافظه نیز درگیر شوید.

آشنایی با معماری Streaming در دات نت

Streaming در دات نت، توسط سه مفهوم: backing store، decorators و adapters در برگرفته شده است. 

کلاسی به نام Stream در دات نت، برای ارائه یکسری متد مشترک برای Reading، Writing و Positioning در نظر گرفته شده است که همچنین کلاس پایه Backing Store Streams و Decorator Streams نیز می‌باشد. 

اعضای کلاس Stream را می‌توان به شکل زیر گروه بندی کرد:


در نظر داشته باشید که Stream ها، دارای اشاره گری به مکان جاری تحت عنوان Pointer نیز می‌باشند. مقدار پیش فرض آن «صفر» می‌باشد و زمانی که شروع به خواندن از Stream کنید، این خواندن از مکانی شروع می‌شود که Pointer به آنجا اشاره می‌کند. به شکل زیر توجه کنید:

اگر قرار باشد 3 بایت اول خوانده شود، لذا حالت زیر را خواهیم داشت: 

همانطور که مشخص است، Pointer مربوط به Stream به اولین خانه‌ای اشاره می‌کند که در Read‌های بعدی قرار است خوانده شود. در نهایت با خواندن دو بایت دیگر، حالت زیر را خواهیم داشت:

برای Reading و Writing متدهای زیر در کلاس System.IO.Stream در نظر گرفته شده‌اند:

(Read(byte[] buffer,int offset,int count

buffer: آرایه‌ای از بایت‌ها برای نگهداری داده‌ی خوانده شده از Stream
offset: برخلاف تصور، اندیسی است که مکان شروع ذخیره سازی در buffer را مشخص می‌کند و نه مکان شروع خواندن از Stream
count: بیشترین تعداد بایت برای خواندن از Stream می‌باشد. با توجه به اینکه ممکن است به انتهای Stream رسیده باشیم یا اینکه در شرایطی مثلا در Network Streamها چه بسا خود Stream تصمیم بگیرد تعداد بایت کمتری از این مقدار Count را برای ما ارائه دهد. از این رو همیشه مقداری که برای Count مشخص می‌کنید همان مقداری نیست که متد Read برای شما برگشت خواهد داد.
return: تعداد بایت‌هایی که خوانده شده است یا اگر به انتهای Stream رسیده باشیم «0» برگشت خواهد داد. از این رو تکه کد زیر برای خواندن کل داده به یکباره، قابل اطمینان نخواهد بود. 

byte[] dataToRead=new byte[stream.Length];
int bytesRead=stream.Read(dataToRead,0,dataToRead.Length);

راه حل جایگزین می‌تواند به شکل زیر باشد:

static byte[] ReadBytes(Stream stream)
    {
        // dataToRead will hold the data read from the stream
        byte[] dataToRead = new byte[stream.Length];
        //this is the total number of bytes read. this will be incremented 
        //and eventually will equal the bytes size held by the stream
        int totalBytesRead = 0;
        //this is the number of bytes read in each iteration (i.e. chunk size)
        int chunkBytesRead = 1;
        while (totalBytesRead < dataToRead.Length && chunkBytesRead > 0)
        {
            chunkBytesRead = stream.Read(dataToRead, totalBytesRead, 
                dataToRead.Length - totalBytesRead);
            totalBytesRead = totalBytesRead + chunkBytesRead;
        }
        return dataToRead;
    }
در کد بالا تا زمانیکه مجموع تعداد بایت‌های خوانده شده کوچکتر از طول Stream باشد و همچنین به انتهای Stream نرسیده باشیم (chunkBytesRead>0)، عملیات خواندن انجام خواهد گرفت. خوشبختانه در کلاس BinaryReader متدی برای این کار در نظر گرفته شده است که در آینده با آنها بیشتر آشنا خواهیم شد.
byte[] data = new BinaryReader (s).ReadBytes (1000);
ReadByte

return: یک بایت را از مکان فعلی که Pointer به آن اشاره می‌کند، می‌خواند. اگر خروجی «-1» باشد، به انتهای Stream رسیده اید.
برخلاف انتظار، خروجی این متد از نوع int می‌باشد؛ چرا که لازم است «-1» را نیز در برگیرد.

CanRead
ممکن است یک Stream از عملیات خواندن پشتیبانی نکند؛ این محدودیت از طریق حالت جاری Backing Store تعیین می‌شود. برای مثال:

با توجه به حالت FileStream که فقط برای Append کردن وهله سازی شده است، امکان خواندن را نخواهید داشت. بنابراین زمانیکه از کلاس شخص ثالثی برای خواندن از Stream استفاده می‌کنید، به‌صلاح است (به منظور Defensive Programming) که از متد CanRead قبل خواندن بهره ببرید.

(Write(byte[] array,int offset,int count

array: آرایه ای از بایت‌ها که قرار است در Stream درج شوند.
offset: اندیس شروع array برای درج کردن در Stream را مشخص می‌کند.
count: بیشترین تعداد بایتی که از array در Stream درج خواهد شد.

WriteByte

برای درج یک بایت در Stream استفاده می‌شود.

CanWrite

برای تشخص پشتیبانی کردن Stream از عملیات درج کردن مورد استفاده قرار خواهد گرفت.

عملیات Seeking 

با انجام هر یک از عملیات Read  و Write برروی Stream، باعث تغییر مکان Pointer مربوط به آن خواهید شد. در صورتیکه نیاز است به صورت انتخابی مکان خاصی از Stream را برای شروع درج کردن یا خواندن انتخاب کنید، Seeking کمک کننده خواهد بود.

باید توجه داشت که پشتیبانی از این عملیات به backing store مورد استفاده وابسته می‌باشد. از این رو باید دانست که MemoryStream و FileStream از Seeking پشتیبانی کرده ولی در مقابل NetworkStream، PipeStream و همچنین Decorator Streams به غیر از BufferedStream قابلیت Seeking را ندارند. BufferedStream با ایجاد پوششی برروی یک Stream به اصطلاح non-seekable، امکان Seeking درون Buffer داخلی خود را مهیا خواهد کرد.

برای عملیات Seeking نیز اعضایی در کلاس پایه System.IO.Stream در نظر گرفته شده است:

(Seek(long Offset,SeekOrigin origin

برای تنظیم مکان Pointer در Stream استفاده خواهد شد. 

(SetLength(long value

متدی برای تنظیم طول Stream، که اگر value ارسال شده کوچکتر از طول فعلی Stream باشد، آن را کوتاه کرده و در غیر این صورت، Stream موردنظر گسترش خواهد یافت. برای استفاده از این متد، Stream مورد نظر باید قابلیت Writing و Seeking را داشته باشد.

Length

پراپرتی فقط خواندنی که طول Stream را مشخص می‌کند. در صورتیکه Stream مورد نظر Seekable باشد، می‌توان از این پراپرتی بهر برد؛ این بدین معنی است که اگر با یک Stream از نوع non-seekable کار می‌کنید، در صورت استفاده از این خصوصیت، تمام بایت‌های Stream خوانده شده و بعد از قرار گرفتن در  یک buffer (به عنوان مثال در memory)، محاسبه خواهد شد.

Position

پراپرتی برای خواندن یا تنظیم مکان فعلی Pointer مربوط به Stream، می‌باشد. برای استفاده از آن لازم است Stream مورد استفاده Seekable باشد.

CanSeek

مشخص می‌کند که Stream مورد استفاده Seekable  می باشد یا خیر.

تفاوت متد Seek و پراپرتی Position برای عملیات Seeking

به طور خلاصه با استفاده از متد Seek انعطاف پذیری بالایی خواهید داشت. با مقدار دهی پراپرتی Position، این مقدار همیشه نسبت به ابتدای Stream در نظر گرفته خواهد شد (شکل زیر)؛ این در حالی است که با استفاده از متد Seek می‌توان مشخص کرد که مقدار Offset تنظیم شده نسبت به ابتدا، مکان جاری و یا انتهای Stream می‌باشد.

مثال:

        using (FileStream fs = File.Create(@"C:\files\testfile3.txt"))
        {
            // position is 0
            long pos = fs.Position;
            // sets the position to 1
            fs.Position = 1; 
         
            byte[] arrbytes = { 100, 101 };
            //writes the content of arrbytes into current position - which is 1
            fs.Write(arrbytes, 0, arrbytes.Length);
            //position is now 3 as its advanced by write
            pos = fs.Position;
            fs.Position = 0;
            byte[] readdata1 = ReadBytes(fs);
        }
در تکه کد بالا قصد داریم تعدادی بایت را در یک فایل متنی ذخیره کنیم. برای نشان دادن عملیات Seeking، ابتدا Position را با عداد «1» تنظیم کرده‌ایم. با استفاده از متد Write عمل درج بایت‌ها با شروع از مکان اندیس «1» را انجام داده‌ایم. در این لحظه، Position عدد «3» را نشان می‌دهد. حال برای خواندن Stream لازم است Position را با «0» مقدار دهی کنیم تا Pointer دوباره به ابتدای Stream اشاره کند و عملیات خواندن را انجام داده‌ایم. اگر تکه کد بالا را دیباگ کنیم، به نتیجه نشان داده شده در شکل زیر خواهیم رسید:

Closing and Flushing 

کلاس پایه System.IO.Stream اینترفیس IDisposable را پیاده سازی کرده است؛ لذا بهتر است برای آزاد سازی منابع از جمله: file handle در FileStream یا socket handle در NetworkStream، بعد از استفاده، متد Dispose آنها را فراخوانی کنید یا با وهله سازی آنها در بدنه using، این فراخوانی به صورت ضمنی انجام شود. 

نکته: باید توجه کنید که با Close (معادل Dispose) شدن decorator streamها ، backing store stream داخلی آنها نیز Close خواهد شد.

با توجه به اینکه I/O عملیات پرهزینه‌ای می‌باشد، برخی از انواع Stream‌ها به منظور بهبود کارآیی از یک مکانیزم بافر داخلی استفاده می‌کنند. به این شکل که عملیات Write، داده را به جای آنکه درون backing store ذخیره سازی کند، درون این بافر ذخیره سازی خواهد کرد. زمانیکه این بافر پر شود یا به صورت صریح متدهای Flush یا Close فراخوانی شده باشند، داده موجود در بافر درون backing store ذخیره خواهد شد. در نتیجه عملیات Read هم می‌تواند به بخشی از داده اصلی که هم اکنون درون بافر می‌باشد، دسترسی سریع‌تری داشته باشد. به عنوان مثال FileStream از این مکانیزم داخلی برخوردار است. سایز پیش فرض این بافر ‏‏4KB (قابل تنظیم است) می‌باشد. برای سایر مواردی که این امکان برایشان وجود ندارد، می‌توان از BufferedStream برای Decorate کردن Stream مورد نظر خود استفاده کرد.

نکته: به صورت پیش فرض، Streamها thread-safe نیستند و امکان خواندن و نوشتن همزمان توسط چند thread برروی یک stream مشترک را نخواهید داشت. برای حل این موضوع، متد استاتیکی در کلاس Stream تحت عنوان Synchronized در نظر گرفته شده است که یک thread-safe wrapper را به برروی stream ورودی در نظر گرفته و آن را به عنوان خروجی برگشت خواهد داد. 

 [HostProtection(SecurityAction.LinkDemand, Synchronization = true)]
    public static Stream Synchronized(Stream stream)
    {
      if (stream == null)
        throw new ArgumentNullException("stream");
      if (stream is Stream.SyncStream)
        return stream;
      return (Stream) new Stream.SyncStream(stream);
    }