C# 8.0 - Nullable Reference Types
زمانیکه یک ! را به عبارتی اضافه میکنیم (به آن عملگر bang هم میگویند!)، به این معنا است که ممکن است این عبارت در جائی از برنامه حاوی نال باشد، اما مطمئن هستیم که در این نقطه از برنامه، هیچگاه نال نخواهد بود. مثال زیر را درنظر بگیرید:
string? s1 = "Hello"; string s2 = s1!;
// Query for all users without tracking them var users = context.Users.AsNoTracking(); // Query for some users without tracking them var someUsers = context.Users .Where(u => u.Name.EndsWith("st")) .AsNoTracking() .ToList(); //Or ... var items = context.Users.AsNoTracking().Where(...);
using System; namespace CS6NewFeatures { class Person { public string FirstName { set; get; } public string LastName { set; get; } public int Age { set; get; } } class Program { static void Main(string[] args) { var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 }; var message = string.Format("Hello! My name is {0} {1} and I am {2} years old.", person.FirstName, person.LastName, person.Age); Console.Write(message); } } }
در C# 6 جهت رفع این مشکلات، راه حلی به نام String interpolation ارائه شدهاست و اگر افزونهی ReSharper یا یکی از افزونههای Roslyn را نصب کرده باشید، به سادگی امکان تبدیل کدهای قدیمی را به فرمت جدید آن خواهید یافت:
در این حالت کد قدیمی فوق، به کد ذیل تبدیل خواهد شد:
static void Main(string[] args) { var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 }; var message = $"Hello! My name is {person.FirstName} {person.LastName} and I am {person.Age} years old."; Console.Write(message); }
عملیاتی که در اینجا توسط کامپایلر صورت خواهد گرفت، تبدیل این کدهای جدید مبتنی بر String interpolation به همان string.Format قدیمی در پشت صحنهاست. بنابراین این قابلیت جدید C# 6 را به کدهای قدیمی خود نیز میتوانید اعمال کنید. فقط کافی است VS 2015 را نصب کرده باشید و دیگر شمارهی دات نت فریم ورک مورد استفاده مهم نیست.
امکان انجام محاسبات با String interpolation
زمانیکه $ در ابتدای رشته قرار گرفت، عبارات داخل {}ها توسط کامپایلر محاسبه و جایگزین میشوند. بنابراین میتوان چنین محاسباتی را نیز انجام داد:
var message2 = $"{Environment.NewLine}Test {DateTime.Now}, {3*2}"; Console.Write(message2);
تغییر فرمت عبارات نمایش داده شده توسط String interpolation
همانطور که با string.Format میتوان نمایش سه رقم جدا کنندهی هزارها را فعال کرد و یا تاریخی را به نحوی خاص نمایش داد، در اینجا نیز همان قابلیتها برقرار هستند و باید پس از ذکر یک : عنوان شوند:
var message3 = $"{Environment.NewLine}{1000000:n0} {DateTime.Now:dd-MM-yyyy}"; Console.Write(message3);
سفارشی سازی String interpolation
اگر متغیر رشتهای معرفی شدهی توسط $ را با یک var مشخص کنیم، نوع آن به صورت پیش فرض، از نوع string خواهد بود. برای نمونه در مثالهای فوق، message و message2 از نوع string تعریف میشوند. اما این رشتههای ویژه را میتوان از نوع IFormattable و یا FormattableString نیز تعریف کرد.
در حقیقت رشتههای آغاز شدهی با $ از نوع IFormattable هستند و اگر نوع متغیر آنها ذکر نشود، به صورت خودکار به نوع FormattableString که اینترفیس IFormattable را پیاده سازی میکند، تبدیل میشوند. بنابراین پیاده سازی این اینترفیس، امکان سفارشی سازی خروجی string interpolation را میسر میکند. برای نمونه میخواهیم در مثال message2، نحوهی نمایش تاریخ را سفارشی سازی کنیم.
class MyDateFormatProvider : IFormatProvider { readonly MyDateFormatter _formatter = new MyDateFormatter(); public object GetFormat(Type formatType) { return formatType == typeof(ICustomFormatter) ? _formatter : null; } class MyDateFormatter : ICustomFormatter { public string Format(string format, object arg, IFormatProvider formatProvider) { if (arg is DateTime) return ((DateTime)arg).ToString("MM/dd/yyyy"); return arg.ToString(); } } }
پس از پیاده سازی این سفارشی کنندهی تاریخ، نحوهی استفادهی از آن به صورت ذیل است:
static string formatMyDate(FormattableString formattable) { return formattable.ToString(new MyDateFormatProvider()); }
در ادامه برای اعمال این سفارشی سازی، فقط کافی است متد formatMyDate را به رشتهی مدنظر اعمال کنیم:
var message2 = formatMyDate($"{Environment.NewLine}Test {DateTime.Now}, {3*2}"); Console.Write(message2);
و اگر تنها میخواهید فرهنگ جاری را عوض کنید، از روش سادهی زیر استفاده نمائید:
public static string faIr(IFormattable formattable) { return formattable.ToString(null, new CultureInfo("fa-Ir")); }
نمونهی کاربردیتر آن اعمال InvariantCulture به String interpolation است:
static string invariant(FormattableString formattable) { return formattable.ToString(CultureInfo.InvariantCulture); }
یک نکته: همانطور که عنوان شد این قابلیت جدید با نگارشهای قبلی دات نت نیز سازگار است؛ اما این کلاسهای جدید را در این نگارشها نخواهید یافت. برای رفع این مشکل تنها کافی است این کلاسهای یاد شده را به صورت دستی در فضای نام اصلی آنها تعریف و اضافه کنید. یک مثال
غیرفعال سازی String interpolation
اگر میخواهید در رشتهای که با $ شروع شده، بجای محاسبهی عبارتی، دقیقا خود آنرا نمایش دهید (و { را escape کنید)، از {{}} استفاده کنید:
var message0 = $"Hello! My name is {person.FirstName} {{person.FirstName}}";
پردازش عبارات شرطی توسط String interpolation
همانطور که عنوان شد، امکان ذکر یک عبارت کامل هم در بین {} وجود دارد (محاسبات، ذکر یک عبارت LINQ، ذکر یک متد و امثال آن). اما در این میان اگر یک عبارت شرطی مدنظر بود، باید بین () قرار گیرد:
Console.Write($"{(person.Age>50 ? "old": "young")}");
امکان خروجی اکسل از گزارشات سیستم، یکی از بایدهای بیشتر سیستمهای اطلاعاتی میباشد؛ یکی از چالشهای اصلی در تولید این نوع خروجی، افزایش مصرف حافظه متناسب با افزایش حجم دیتا میباشد. از آنجاییکه بیشتر راهکارهای موجود از جمله ClosedXml یا Epplus کل ساختار را ابتدا تولید کرده و اصطلاحا خروجی مورد نظر را بافر میکنند، برای حجم بالای اطلاعات مناسب نخواهند بود. راهکار برای خروجی CSV به عنوان مثال خیلی سرراست میباشد و میتوان با چند خط کد، به نتیجه دلخواه از طریق مکانیزم Streaming رسید؛ ولی ساختار Excel به سادگی فرمت CSV نیست و برای مثال فرمت Excel Workbook با پسوند xlsx یک بسته Zip شدهای از فایلهای XML میباشد.
معرفی MiniExcel
MiniExcel یک کتابخانه سورس باز با هدف به حداقل رساندن مصرف حافظه در زمان پردازش فایلهای Excel در دات نت میباشد. در مقایسه با Aspose از منظر امکانات شاید حرفی برای گفتن نداشته باشد، ولی از جهت خواندن اطلاعات فایلهای Excel با قابلیت پشتیبانی از LINQ و Deferred Execution در کنار مصرف کم حافظه و جلوگیری از مشکل OOM خیلی خوب عمل میکند. در تصویر زیر مشخص است که برای عمده عملیات پیادهسازی شده، از استریمها بهره برده شده است.
همچنین در زیر مقایسهای روی خروجی ۱ میلیون رکورد با تعداد ۱۰ ستون در هر ردیف انجام شدهاست که قابل توجه میباشد:
Logic : create a total of 10,000,000 "HelloWorld" excel
LibraryMethodMax Memory UsageMean | |||
MiniExcel | 'MiniExcel Create Xlsx' | 15 MB | 11.53181 sec |
Epplus | 'Epplus Create Xlsx' | 1,204 MB | 22.50971 sec |
OpenXmlSdk | 'OpenXmlSdk Create Xlsx' | 2,621 MB | 42.47399 sec |
ClosedXml | 'ClosedXml Create Xlsx' | 7,141 MB | 140.93992 sec |
به شدت API خوش دستی برای استفاده دارد و شاید مطالعه سورس کد آن از جهت طراحی نیز درس آموزی داشته باشد. در ادامه چند مثال از مستندات آن را میتوانید ملاحظه کنید:
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); MiniExcel.SaveAs(path, new[] { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} });
// DataReader export multiple sheets (recommand by Dapper ExecuteReader) using (var cnn = Connection) { cnn.Open(); var sheets = new Dictionary<string,object>(); sheets.Add("sheet1", cnn.ExecuteReader("select 1 id")); sheets.Add("sheet2", cnn.ExecuteReader("select 2 id")); MiniExcel.SaveAs("Demo.xlsx", sheets); }
طراحی یک ActionResult سفارشی برای استفاده از MiniExcel
برای این منظور نیاز است تا Stream مربوط به Response درخواست جاری را در اختیار این کتابخانه قرار دهیم و از سمت دیگر دیتای مورد نیاز را به نحوی که بافر نشود و از طریق مکانیزم Streaming در EF (استفاده از Deferred Execution و Enumerableها) مهیا کنیم. برای امکان تعویض پذیری (این سناریو در پروژه واقعی و باتوجه به جهت وابستگیها میتواند ضروری باشد) از دو واسط زیر استفاده خواهیم کرد:
public interface IExcelDocumentFactory { ILargeExcelDocument CreateLargeDocument(IEnumerable<ExcelColumn> headers, Stream stream); } public interface ILargeExcelDocument : IAsyncDisposable, IDisposable { Task Write<T>( PaginatedEnumerable<T> items, int count, int sizeLimit, CancellationToken cancellationToken = default) where T : notnull; }
متد CreateLargeDocument یک وهله از ILargeExcelDocument را در اختیار مصرف کننده قرار میدهد که قابلیت نوشتن روی آن از طریق متد Write را خواهد داشت. روش واکشی دیتا از طریق Delegate تعریف شده با نام PaginatedEnumerable به مصرف کننده محول شدهاست که در ادامه امضای آن را میتوانید مشاهده کنید:
public delegate IEnumerable<T> PaginatedEnumerable<out T>(int page, int pageSize);
در ادامه پیادهسازی واسط ILargeExcelDocument برای MiniExcel به شکل زیر خواهد بود:
internal sealed class MiniExcelDocument(Stream stream, IEnumerable<ExcelColumn> columns) : ILargeExcelDocument { private const int SheetLimit = 1_048_576; private bool _disposedValue; public async Task Write<T>( PaginatedEnumerable<T> items, int count, int sizeLimit, CancellationToken cancellationToken = default) where T : notnull { ThrowIfDisposed(); // TODO: apply sizeLimit var properties = FastReflection.Instance.GetProperties(typeof(T)) .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); var sheets = new Dictionary<string, object>(); var index = 1; while (count > 0) { cancellationToken.ThrowIfCancellationRequested(); IEnumerable<Dictionary<string, object>> reader = items(index, SheetLimit) .Select(item => { cancellationToken.ThrowIfCancellationRequested(); return columns.ToDictionary(h => h.Title, h => ValueOf(item, h.Name, properties)); }); sheets.Add($"sheet_{index}", reader); count -= SheetLimit; index++; } // This part is forward-only, and we are pretty sure that streaming will happen without buffering. await stream.SaveAsAsync(sheets, cancellationToken: cancellationToken); } private void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects) } // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null _disposedValue = true; } } ~MiniExcelDocument() { Dispose(disposing: false); } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } public async ValueTask DisposeAsync() { Dispose(); await ValueTask.CompletedTask; } private void ThrowIfDisposed() { if (!_disposedValue) return; throw new ObjectDisposedException(nameof(MiniExcelDocument)); } private static object ValueOf<T>(T record, string prop, IDictionary<string, FastPropertyInfo> properties) where T : notnull { var property = properties[prop] ?? throw new InvalidOperationException($"There is no property with given name [{prop}]"); return NormalizeValue(property.GetValue?.Invoke(record)); } private static object NormalizeValue(object? value) { if (value == null) return null!; return value switch { DateTime dateTime => dateTime.ToShortPersianDateTimeString(), TimeSpan time => time.ToString(@"hh\:mm\:ss"), DateOnly dateTime => dateTime.ToShortPersianDateString(false), TimeOnly time => time.ToString(@"hh\:mm\:ss"), bool boolean => boolean ? "بلی" : "خیر", IEnumerable<object> values => string.Join(',', values.Select(NormalizeValue).ToList()), Enum enumField => enumField.GetEnumStringValue(), _ => value }; } }
در بدنه متد Write باتوجه به تعداد کل رکوردها، یک کوئری برای هر شیت از طریق فراخوانی متد منتسب به پارامتر items اجرا خواهد شد؛ توجه کنید که اجرای این کوئری مشخصا به تعویق افتاده و تا زمان اولین MoveNext، اجرایی صورت نخواهد گرفت (مفهوم Deferred Execution). به این ترتیب باقی کارها از جمله فرمت کردن مقادیر در سمت برنامه و از طریق Linq To Object انجام خواهد شد. همچنین پیادهسازی Factory مرتبط با آن به شکل زیر خواهد بود:
internal sealed class ExcelDocumentFactory : IExcelDocumentFactory { public ILargeExcelDocument CreateLargeDocument(IEnumerable<ExcelColumn> columns, Stream stream) { return new MiniExcelDocument(stream, columns); } }
در ادامه ActionResult سفارشی برای گرفتن خروجی اکسل را به شکل زیر می توان پیادهسازی کرد:
public class ExcelExportResult<T>(PaginatedEnumerable<T> items, int count, ExportMetadata metadata) : ActionResult where T : notnull { private const string ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private const string Extension = ".xlsx"; private const int SizeLimit = int.MaxValue; private readonly IReadOnlyList<FastPropertyInfo> _properties = FastReflection.Instance.GetProperties(typeof(T)); public override async Task ExecuteResultAsync(ActionContext context) { var sp = context.HttpContext.RequestServices; var factory = sp.GetRequiredService<IExcelDocumentFactory>(); var disposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment); disposition.SetHttpFileName(MakeFilename()); context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = disposition.ToString(); context.HttpContext.Response.Headers.Append(HeaderNames.ContentType, ContentType); context.HttpContext.Response.StatusCode = StatusCodes.Status200OK; //TODO: deal with exception, because our global exception handling cannot take into account while the response is started. await using var bodyStream = context.HttpContext.Response.BodyWriter.AsStream(); await context.HttpContext.Response.StartAsync(context.HttpContext.RequestAborted); await using (var document = factory.CreateLargeDocument(MakeColumns(), bodyStream)) { await document.Write(items, count, SizeLimit, context.HttpContext.RequestAborted); } await context.HttpContext.Response.CompleteAsync(); } private string MakeFilename() { return $"{metadata.Title} - {DateTime.UtcNow.ToEpochSeconds()}{Extension}"; } private IEnumerable<ExcelColumn> MakeColumns() { var types = _properties.ToDictionary(p => p.Name, p => p.PropertyType, StringComparer.OrdinalIgnoreCase); return metadata.Fields.Select(f => { var type = types[f.Name]; type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum || type == typeof(DateOnly) || type == typeof(TimeOnly) || type == typeof(bool) || type == typeof(TimeSpan) || type == typeof(DateTime)) { type = typeof(string); } return new ExcelColumn(f.Name, f.Title, type); }); } }
در اینجا از طریق ExportMetadata که از سمت کاربر تعیین میشود، مشخص خواهد شد که کدام فیلدها در فایل نهایی حضور داشته باشند. در بدنه متد ExecuteResultAsync یکسری هدر مرتبط با کار با فایلها تنظیم شدهاست و سپس از طریق BodyWriter و متد AsStream به استریم مورد نظر دست یافته و در اختیار متد Write مربوط به document ایجاد شده، قرار دادهایم. یک نمونه استفاده از آن برای موجودیت فرضی مشتری می تواند به شکل زیر باشد:
[ApiController, Route("api/customers")] public class CustomersController(IDbContext dbContext) : ControllerBase { [HttpGet("export")] public async Task<ActionResult> ExportCustomers([FromQuery] ExportMetadata metadata, CancellationToken cancellationToken) { var count = await dbContext.Set<Customer>().CountAsync(cancellationToken); return this.Export( (page, pageSize) => dbContext.Set<Customer>() .OrderBy(c => c.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .AsNoTracking() .AsEnumerable(), // Enable streaming instead of buffering through deferred execution count, metadata); } }
در اینجا از طریق Extension Method مهیا شده روش کوئری کردن برای هر شیت را مشخص کردهایم؛ نکته مهم در ایجاد استفاده از متد AsEnumerable می باشد که در عمل یک Type Casting انجام می دهد که باقی متدهای استفاده شده روی خروجی، از طریق Linq To Object اعمال شود و همچنین نیاز به استفاده از ToList و یا موارد مشابه را نخواهیم داشت. نمونه درخواست GET برای این API می تواند به شکل زیر باشد:
http://localhost:5118/api/customers/export?Title=Test&Fields[0].Name=FirstName&Fields[0].Title=First name&Fields[1].Name=LastName&Fields[1].Title=Last name&Fields[2].Name=BirthDate&Fields[2].Title=BirthDate
سورس کد مثال قابل اجرا از طریق مخزن زیر قابل دسترس می باشد:
https://github.com/rabbal/large-excel-streaming
در این مثال در زمان آغاز برنامه، ۱۰ میلیون رکورد در جدول Customer ثبت خواهد شد که در ادامه می توان از آن خروجی Excel تهیه کرد.
نکته مهم: توجه داشته باشید که استفاده از این روش قابلیت از سرگیری مجدد برای دانلود را نخواهد داشت و شاید بهتر است این فرآیند را از طریق یک Job انجام داده و با استفاده از قابلیتهای Multipart Upload مربوط به یک BlobStroage مانند Minio، خروجی مورد نظر از قبل ذخیره کرده و لینک دانلودی را در اختیار کاربر قرار دهید.
- Success
- Error
- Warning
- Verbose
- Debug
- Information
cmdlet | Name | Id |
Write-Output | Success | 1 |
Write-Error | Error | 2 |
Write-Warning | Warning | 3 |
Write-Verbose | Verbose | 4 |
Write-Debug | Debug | 5 |
Write-Information | Information | 6 |
به جز دو مورد اول، بقیه cmdletها خروجی را به صورت پیشفرض درون کنسول نمایش نمیدهند. به عنوان مثال اسکریپت زیر را در نظر بگیرید:
Write-Output 'Output' Write-Error 'This is an error' Write-Warning 'This is a warning' Write-Verbose 'This is verbose' Write-Debug 'This is Debug' Write-Information 'This is information'
با اجرای اسکریپت فوق خروجی زیر را خواهیم داشت:
Output Write-Error: This is an error WARNING: This is a warning
همانطور که مشاهده میکنید سه cmdlet فوق، خروجی را درون کنسول نمایش ندادهاند. این رفتار توسط مفهومی تحت عنوان Action Preference قابل تنظیم است که در واقع یک Enum است با مقدار زیر:
6 | Break | |
رخداد به صورت عادی مدیریت شده و برنامه ادامه پیدا میکند | 2 | Continue |
به طور کلی از رخداد صرفنظر خواهد شد؛ بدون اینکه چیزی در استریم نمایش داده شود | 4 | Ignore |
سوال پرسیده خواهد شد که برنامه را ادامه دهد یا متوقف کند | 3 | Inquire |
به طور کلی از رخداد صرفنظر خواهد شد | 0 | SilentlyContinue |
دستور را متوقف خواهد کرد | 1 | Stop |
دستور به نوعی معلق خواهد شد | 5 | Suspend |
بنابراین با تغییر Action Preference برای هر کدام از cmdletها میتوانیم رفتار اسکریپت قبلی را تغییر دهیم:
Write-Output 'Output' Write-Error 'This is an error' Write-Warning 'This is a warning' $VerbosePreference = 'Continue' Write-Verbose 'This is verbose' $DebugPreference = 'Continue' Write-Debug 'This is Debug' $InformationPreference = 'Continue' Write-Information 'This is information'
اکنون اگر اسکریپت فوق را اجرا کنید، سه خروجی آخر را نیز مشاهده خواهید کرد:
Output Write-Error: This is an error WARNING: This is a warning VERBOSE: This is verbose DEBUG: This is Debug This is information
هر کدام از استریمهای فوق قابل redirect شدن نیز هستند؛ برای اینکار میتوانیم از redirect operatorهایی که در PowerShell پشتیبانی میشود استفاده کنیم:
> >> >&1
به عنوان مثال میتوانیم تمام خطاها یا هشدارهای درون یک اسکریپت را به یک فایل منتقل کنیم:
./script.ps1 2>&1 > .\logs.txt
یا میتوانیم تمام Success streamها را به یک فایل هدایت کنیم:
.\script.ps1 > script.log
ارسال تمام Success, Warning, Errorها به یک فایل:
&{ Write-Warning "hello" Write-Error "hello" Write-Output "hi" } 3>&1 2>&1 > C:\Temp\redirection.log
ارسال تمام استریمها به یک فایل:
.\script.ps1 *> script.log
همچنین میتوانیم استریمی را به اصطلاح suppress کنیم که در خروجی نمایش داده نشود:
./script.ps1 1> $null 2> $null ./script.ps1 *> $null
از تکنیک فوق برای drop کردن خروجیهایی که نمیخواهیم نمایش داده شوند، استفاده میشود. در کد فوق دو Idهای ۱ و ۲ را به متغیر ویژهی null هدایت کردهایم؛ همچنین میتوانستیم از یک رشتهی خالی نیز بجای null استفاده کنیم. در خط بعدی از * استفاده کردهایم که به معنای تمامی استریمهای موجود است؛ با اینکار چیزی در خروجی نمایش داده نخواهد شد. یک روش دیگر برای drop کردن، استفاده از دستور Out-Null است:
Get-ChildItem | Out-Null
لازم به ذکر است که این cmdlet تا قبل از نسخه ۶ خیلی کند بود؛ زیرا همانند دیگر cmdletهای درون pipeline میبایست یک ورودی (InputObject) را دریافت کند که باعث میشد هزینهی پردازشی بالایی داشته باشد. اما در نسخه ۶ به بعد این مشکل رفع شدهاست و پارزر به محض رسیدن به این keyword به صورت کلی خروجی را discard میکند بدون اینکه Out-Null را فراخوانی کند؛ در واقع این cmdlet یک hint برای پارزر است. روش دیگر برای drop کردن خروجی، انتساب نتیجه یک دستور به متغییر null است:
New-Item -Type Directory -Path $path | Out-Null $null = New-Item -Type Directory -Path $path
همچنین میتوانیم خروجی یک دستور را به void تبدیل کنیم؛ که نتیجه مشابه با تکنیکهای فوق دارد:
[void](New-Item -Name test -ItemType Directory)
یک نکته در مورد Out-Null
در loopهای بزرگ ممکن است Out-Null حتی در PowerShell 7.x هم کند عمل کند:
PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { $i | Out-Null } } | Select-Object TotalSeconds TotalSeconds ------------ 4.3056315 PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { $null = $i } } | Select-Object TotalSeconds TotalSeconds ------------ 1.1210884 PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { [void]$i } } | Select-Object TotalSeconds TotalSeconds ------------ 1.130507 PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { $i > $null } } | Select-Object TotalSeconds TotalSeconds ------------ 1.3832427
0.Visual Studio 2017 15.9 منتشر شد
Summary of Notable New Features in 15.9
- You can now import and export an installation configuration file that specifies which workloads and components should be installed with an instance of Visual Studio.
- We have improved the debugging experience for NuGet packages using the new symbol package format (.snupkg).
- Step back in debugger is now available in C++ for Enterprise customers.
- C++ IntelliSense now responds to changes in the remote environment for both CMake and MSBuild projects targeting Linux.
- We have made updates to UWP Desktop Bridge framework packages and added support for ARM64 C++ Native Desktop scenarios.
- We added support for the range-v3 library with the MSVC 15.9 compiler.
- We fixed several bugs in the F# compiler and F# tools.
- Language service support for new TypeScript features for semantic file renaming and project references.
- Improved Node.js development by updating Vue.js templates and adding support for unit testing using the Jest framework.
- We added SharePoint 2019 project templates, so you can migrate existing SharePoint 2013 and 2016 projects to SharePoint 2019.
- Visual Studio Tools for Xamarin now supports Xcode 10.
- We made improvements to the Xamarin.Android build performance.
- We have added and improved features for Universal Windows Platform developers, including ARM64 support, the latest preview SDK, better debugging of Desktop Bridge applications, and XAML Designer improvements.
- Substantial improvements were made to the experience of using authenticated package feeds.
- There is now support for lock file to enable repeatable restore for PackageReference based projects.
- We have added support for the new license format for NuGet packages.
- We have introduced NuGet client policies in Visual Studio which enables you to lock down environments such that only trusted packages can be installed.
- We made the use of .NET Core within Visual Studio more predictable.
Top Issues Fixed in 15.9
- No way to change "Find All References" background color.
- "Visual C++ Resource Editor Package" load failed.
- VS2017 v15.8 Build does not start if XAML files are not manually saved first.
- Installation failed - manifest signature verification failed.
- Update 15.8.6 breaks Installer Projects.
- Scrolling up with the arrow key causes Visual Studio to page up.
- After updating to 15.8.1, data tip does not show when debugging.
- System.InvalidProgramException: Common Language Runtime detected an invalid program..
- Solution Explorer does not remain pinned after closing Visual Studio.
- Navigation bar in editor has trouble handling long method names.
کتاب Akka.NET مختصر و مفید
Akka.NET is an open-source actor model framework written exclusively for
Microsoft.NET in C# and compatible with .NET Core. It simplifies the
building of scalable, concurrent, high-throughput, and low-latency
systems, making life for software developers a bit easier. Zoran
Maksimovic's Akka.NET Succinctly will show readers what an actor
model is and how to work with actors in Akka.NET, taking them from an
actor's lifecycle through to unit testing.