The conclusion of the analysis:
C# Wasm AOT still
has a long way to become a general and performant client side web
programming platform. Note: The usage of the Uno.Wasm.Bootstrap
toolchain may have affected the performance of some of the benchmarks.
Thus, this analysis should not be regarded as a benchmark of the
finalized product. However, note that the Uno platform is using ".NET 6
WebAssembly Mixed mode AOT/Interpreter" (source).
نظرات اشتراکها
روش امن نگهداری پسورد کاربران
پیاده سازی روش گفته شده در این سایت :
/* * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm). * Copyright (c) 2013, Taylor Hornby * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Text; using System.Security.Cryptography; namespace PasswordHash { /// <summary> /// Salted password hashing with PBKDF2-SHA1. /// Author: havoc AT defuse.ca /// www: http://crackstation.net/hashing-security.htm /// Compatibility: .NET 3.0 and later. /// </summary> public class PasswordHash { // The following constants may be changed without breaking existing hashes. public const int SALT_BYTE_SIZE = 24; public const int HASH_BYTE_SIZE = 24; public const int PBKDF2_ITERATIONS = 1000; public const int ITERATION_INDEX = 0; public const int SALT_INDEX = 1; public const int PBKDF2_INDEX = 2; /// <summary> /// Creates a salted PBKDF2 hash of the password. /// </summary> /// <param name="password">The password to hash.</param> /// <returns>The hash of the password.</returns> public static string CreateHash(string password) { // Generate a random salt RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider(); byte[] salt = new byte[SALT_BYTE_SIZE]; csprng.GetBytes(salt); // Hash the password and encode the parameters byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); return PBKDF2_ITERATIONS + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash); } /// <summary> /// Validates a password given a hash of the correct one. /// </summary> /// <param name="password">The password to check.</param> /// <param name="correctHash">A hash of the correct password.</param> /// <returns>True if the password is correct. False otherwise.</returns> public static bool ValidatePassword(string password, string correctHash) { // Extract the parameters from the hash char[] delimiter = { ':' }; string[] split = correctHash.Split(delimiter); int iterations = Int32.Parse(split[ITERATION_INDEX]); byte[] salt = Convert.FromBase64String(split[SALT_INDEX]); byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]); byte[] testHash = PBKDF2(password, salt, iterations, hash.Length); return SlowEquals(hash, testHash); } /// <summary> /// Compares two byte arrays in length-constant time. This comparison /// method is used so that password hashes cannot be extracted from /// on-line systems using a timing attack and then attacked off-line. /// </summary> /// <param name="a">The first byte array.</param> /// <param name="b">The second byte array.</param> /// <returns>True if both byte arrays are equal. False otherwise.</returns> private static bool SlowEquals(byte[] a, byte[] b) { uint diff = (uint)a.Length ^ (uint)b.Length; for (int i = 0; i < a.Length && i < b.Length; i++) diff |= (uint)(a[i] ^ b[i]); return diff == 0; } /// <summary> /// Computes the PBKDF2-SHA1 hash of a password. /// </summary> /// <param name="password">The password to hash.</param> /// <param name="salt">The salt.</param> /// <param name="iterations">The PBKDF2 iteration count.</param> /// <param name="outputBytes">The length of the hash to generate, in bytes.</param> /// <returns>A hash of the password.</returns> private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes) { Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt); pbkdf2.IterationCount = iterations; return pbkdf2.GetBytes(outputBytes); } } }
نوعهای ارجاعی (Reference Types) در #C، همیشه نالپذیر بودهاند؛ در مقابل نوعهای مقداری (value types) مانند DateTime که برای نالپذیر کردن آنها باید یک علامت سؤال را در حین تعریف نوع آنها ذکر کرد تا تبدیل به یک نوع نالپذیر شود (DateTime? Created). بنابراین عنوانی مانند «نوعهای ارجاعی نالنپذیر» شاید آنچنان مفهوم نباشد.
خالق Null در زبانهای برنامه نویسی، آنرا یک اشتباه چند میلیارد دلاری میداند! و به عنوان یک توسعه دهندهی دات نت، غیرممکن است که در حین اجرای برنامههای خود تابحال به null reference exception برخورد نکرده باشید. هدف از ارائهی قابلیت جدید «نوعهای ارجاعی نالنپذیر» در C# 8.0، مقابلهی با یک چنین مشکلاتی است و خصوصا غنی سازی IDEها برای ارائهی اخطارهایی پیش از کامپایل برنامه، در مورد قسمتهایی از کد که ممکن است سبب بروز null reference exception شوند.
فعالسازی «نوعهای ارجاعی نالنپذیر»
قابلیت «نوعهای ارجاعی نالنپذیر» به صورت پیشفرض غیرفعال است. برای فعالسازی آن میتوان فایل csproj را به صورت زیر، با افزودن خاصیت NullableContextOptions، ویرایش کرد:
یک نکته: در نگارشهای بعدی NET Core SDK. و همچنین ویژوال استودیو (از نگارش 16.2.0 به بعد)، خاصیت NullableContextOptions به صرفا Nullable تغییر نام یافته و ساده شدهاست. بنابراین اگر در این نگارشها به خطاهای ذیل برخوردید:
صرفا به معنای استفادهی از نام قدیمی این ویژگی است که باید به Nullable تغییر پیدا کند:
اما در زمان نگارش این مطلب که 3.0.100-preview5-011568 در دسترس است، فعلا همان نام قدیمی NullableContextOptions کار میکند.
تغییر ماهیت نوعهای ارجاعی #C با فعالسازی NullableContextOptions
در #C ای که ما میشناسیم، رشتهها قابلیت پذیرش نال را دارند و همچنین ذکر آنها به صورت nullable بیمعنا است. اما پس از فعالسازی ویژگی نوعهای ارجاعی نالنپذیر، اکنون عکس آن رخ میدهد. رشتهها نالنپذیر میشوند؛ اما میتوان در صورت نیاز، آنها را nullable نیز تعریف کرد.
یک مثال: بررسی تاثیر فعالسازی NullableContextOptions بر روی یک پروژه
کلاس زیر را در نظر بگیرید:
با فعالسازی خاصیت NullableContextOptions، بلافاصله اخطار زیر در IDE ظاهر میشود (اگر ظاهر نشد، یکبار پروژه را بسته و مجددا بارگذاری کنید):
در این کلاس، دو سازنده وجود دارند که یکی MiddleName را دریافت میکند و دیگری خیر. در اینجا کامپایلر تشخیص دادهاست که چون در سازندهی اولی که MiddleName را دریافت نمیکند، مقدار پیشفرض خاصیت MiddleName، نال خواهد بود و همچنین ما NullableContextOptions را نیز فعال کردهایم، بنابراین این خاصیت دیگر به صورت معمول و متداول یک نوع ارجاعی نالپذیر عمل نمیکند و دیگر نمیتوان نال را به عنوان مقدار پیشفرض آن، به آن نسبت داد. به همین جهت اخطار فوق ظاهر شدهاست.
برای رفع این مشکل:
به کامپایلر اعلام میکنیم: «میدانیم که MiddleName میتواند نال هم باشد» و آنرا در این زمینه راهنمایی میکنیم:
پس از این تغییر، اخطار فوق که ذیل سازندهی اول کلاس Person ظاهر شده بود، محو میشود. اما اکنون مجددا کامپایلر، در جائیکه میخواهیم از آن استفاده کنیم:
اخطارهایی را صادر میکند:
در اینجا در متد محلی (local function) تعریف شده، سعی در دسترسی به خاصیت MiddleName وجود دارد و اکنون با تغییر جدیدی که اعمال کردیم، به صورت نالپذیر تعریف شدهاست.
همچنین در سطر بعدی آن نیز نتیجهی نهایی middleName، مورد استفاده قرار گرفتهاست که آن نیز مشکلدار تشخیص داده شدهاست.
مشکل اولین سطر را به این صورت میتوانیم برطرف کنیم:
در اینجا بجای ذکر صریح نوع string، از var استفاده شدهاست. پیشتر با ذکر صریح نوع string، آنرا یک رشتهی نالنپذیر تعریف کرده بودیم. اما اکنون چون person.MiddleName نالپذیر تعریف شدهاست، var نیز به صورت خودکار به این رشتهی نالپذیر اشاره میکند.
اما مشکل سطر دوم هنوز باقی است:
علت اینجا است که متغیر middleName نیز اکنون ممکن است مقدار نال را داشته باشد. برای رفع این مشکل میتوان از اپراتور .? استفاده کرد و سپس اگر مقدار نهایی این عبارت نال بود، مقدار صفر را بازگشت میدهیم:
هدف از این قابلیت و ویژگی کامپایلر، کمک کردن به توسعه دهندهها جهت نوشتن کدهایی امنتر و مقاومتر به null reference exceptionها است.
امکان خاموش و روشن کردن ویژگی نوعهای ارجاعی نالنپذیر به صورت موضعی
زمانیکه خاصیت NullableContextOptions را فعال میکنیم، بر روی کل پروژه تاثیر میگذارد. برای مثال اگر یک چنین قابلیتی را بر روی پروژههای قدیمی خود فعال کنید، با صدها اخطار مواجه خواهید شد. به همین جهت است که این ویژگی حتی با فعالسازی C# 8.0 و انتخاب آن، به صورت پیشفرض غیرفعال است. بنابراین برای اینکه بتوان پروژههای قدیمی را قدم به قدم و سر فرصت، «مقاومتر» کرد، میتوان تعیین کرد که کدام قسمت، تحت تاثیر این ویژگی قرار بگیرد و کدام قسمتها خیر:
در اینجا میتوان با استفاده از compiler directive جدید nullable# به کامپایلر اعلام کرد که از این قسمت صرفنظر کن. مقدار آن میتواند disable و یا enable باشد.
مجبور ساختن خود به «مقاوم سازی» برنامه
اگر NullableContextOptions را فعال کنید، کامپایلر صرفا یکسری اخطار را در مورد مشکلات احتمالی صادر میکند؛ اما برنامه هنوز کامپایل میشود. برای اینکه خود را مقید به «مقاوم سازی» برنامه کنیم، میتوانیم با فعالسازی ویژگی TreatWarningsAsErrors در فایل csprj، این اخطارها را تبدیل به خطای کامپایلر کرده و از کامپایل برنامه جلوگیری کنیم:
البته TreatWarningsAsErrors تمام اخطارهای برنامه را تبدیل به خطا میکند. اگر میخواهید انتخابیتر عمل کنید، میتوان از خاصیت WarningsAsErrors استفاده کرد:
آیا اگر برنامهای با C# 7.0 کامپایل شود، کتابخانههای تهیه شدهی با C# 8.0 را میتواند استفاده کند؟
پاسخ: بله. از دیدگاه برنامههای قدیمی، کتابخانههای تهیه شدهی با C# 8.0، تفاوتی با سایر کتابخانه ندارند. آنها نوعهای نالپذیر جدید را مانند ?string مشاهده نمیکنند؛ آنها فقط string را مشاهده میکنند و روش کار کردن با آنها نیز همانند قبل است. بدیهی است در این حالت از مزایای کامپایلر C# 8.0 در تشخیص زود هنگام مشکلات برنامه محروم خواهند بود؛ اما عملکرد برنامه تفاوتی نمیکند.
وضعیت برنامهی C# 8.0 ای که از کتابخانههای C# 7.0 و یا قبل از آن استفاده میکند، چگونه خواهد بود؟
چون کتابخانههای قدیمیتر از مزایای کامپایلر C# 8.0 استفاده نمیکنند، خروجیهای آن بدون بروز خطایی توسط کامپایلر C# 8.0 استفاده میشوند؛ چون حجم اخطارهای صادر شدهی در این حالت بیش از حد خواهد بود. یعنی این بررسیهای کامپایلر صرفا برای کتابخانههای جدید فعال هستند و نه برای کتابخانههای قدیمی.
مهارتهای مواجه شدن با اخطارهای ناشی از فعالسازی NullableContextOptions
در مثالی که بررسی شد، یک نمونه از روشهای مواجه شدن با اخطارهای ناشی از فعالسازی ویژگی نوعهای ارجاعی نالنپذیر را بررسی کردیم. در ادامه روشهای تکمیلی دیگری را بررسی میکنیم.
1- هرجائیکه قرار است متغیر ارجاعی نالپذیر باشد، آنرا صراحتا اعلام کنید.
این مثال را پیشتر بررسی کردیم. با فعالسازی ویژگی نوعهای ارجاعی نالنپذیر، ماهیت آنها نیز تغییر میکند و دیگر نمیتوان به آنها null را انتساب داد. اگر نیاز است حتما اینکار صورت گیرد، آنها را توسط ? به صورت nullable تعریف کنید.
نمونهی دیگر آن مثال زیر است:
در اینجا Address یک نوع ارجاعی نالپذیر است. بنابراین حاصل Address?.Country میتواند نال باشد و به Country نالنپذیر قابل انتساب نیست. برای رفع این مشکل کافی است دقیقا مشخص کنیم که این رشته نیز نالپذیر است:
البته در این حالت باید به مثال زیر دقت داشت:
چون node در اینجا توسط var تعریف شدهاست، دقیقا نوع this را که non-nullable است، پیدا میکند. بنابراین بعدها نمیتوان به آن null را انتساب داد. اگر چنین موردی نیاز بود، باید صریحا نوع آنرا بدو امر، nullable تعریف کرد؛ چون هنوز امکان تعریف ?var میسر نیست:
2- نوعهای خود را مقدار دهی اولیه کنید.
در مثال زیر:
در این حالت چون خاصیت Name، در سازندهی کلاس مقدار دهی اولیه نشدهاست، یک اخطار صادر میشود که بیانگر احتمال نال بودن آن است. یک روش مواجه شدن با این مشکل، تعریف آن به صورت یک خاصیت نالپذیر است:
یا یک استثناء را صادر کنید:
به این ترتیب کامپایلر میداند که اگر نام دریافتی نال بود، دقیقا باید چگونه رفتار کند.
البته در این حالت برای مقدار دهی اولیهی Name، حتما نیاز به تعریف یک سازندهاست و در این حالت کدهایی را که از سازندهی پیشفرض استفاده کرده بودند (مانند new Person { Name = "Vahid" })، باید تغییر دهید.
راهحل دیگر، مقدار دهی اولیهی این خواص بدون تعریف یک سازندهی اضافی است:
برای مثال میتوان از مقادیر خالی زیر برای مقدار دهی اولیهی رشتهها، آرایهها و مجموعهها استفاده کرد:
یا حتی میتوان اشیاء دیگر را نیز به صورت زیر مقدار دهی اولیه کرد:
البته در این حالت باید مفهوم فلسفی «خالی بودن» را پیش خودتان تفسیر و تعریف کنید که دقیقا مقصود از یک آدرس خالی چیست؟ به همین جهت شاید تعریف این شیء به صورت nullable بهتر باشد.
خالق Null در زبانهای برنامه نویسی، آنرا یک اشتباه چند میلیارد دلاری میداند! و به عنوان یک توسعه دهندهی دات نت، غیرممکن است که در حین اجرای برنامههای خود تابحال به null reference exception برخورد نکرده باشید. هدف از ارائهی قابلیت جدید «نوعهای ارجاعی نالنپذیر» در C# 8.0، مقابلهی با یک چنین مشکلاتی است و خصوصا غنی سازی IDEها برای ارائهی اخطارهایی پیش از کامپایل برنامه، در مورد قسمتهایی از کد که ممکن است سبب بروز null reference exception شوند.
فعالسازی «نوعهای ارجاعی نالنپذیر»
قابلیت «نوعهای ارجاعی نالنپذیر» به صورت پیشفرض غیرفعال است. برای فعالسازی آن میتوان فایل csproj را به صورت زیر، با افزودن خاصیت NullableContextOptions، ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> <LangVersion>8.0</LangVersion> <NullableContextOptions>enable</NullableContextOptions> </PropertyGroup> </Project>
CS8632: The annotation for nullable reference types should only be used in code within a ‘#nullable’ context. CS8627: A nullable type parameter must be known to be a value-type or non-nullable reference type. Consider adding a ‘class’, ‘struct’ or type constraint.
<PropertyGroup> <LangVersion>preview</LangVersion> <Nullable>enable</Nullable> </PropertyGroup>
تغییر ماهیت نوعهای ارجاعی #C با فعالسازی NullableContextOptions
در #C ای که ما میشناسیم، رشتهها قابلیت پذیرش نال را دارند و همچنین ذکر آنها به صورت nullable بیمعنا است. اما پس از فعالسازی ویژگی نوعهای ارجاعی نالنپذیر، اکنون عکس آن رخ میدهد. رشتهها نالنپذیر میشوند؛ اما میتوان در صورت نیاز، آنها را nullable نیز تعریف کرد.
یک مثال: بررسی تاثیر فعالسازی NullableContextOptions بر روی یک پروژه
کلاس زیر را در نظر بگیرید:
public class Person { public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } public Person(string first, string last) => (FirstName, LastName) = (first, last); public Person(string first, string middle, string last) => (FirstName, MiddleName, LastName) = (first, middle, last); public override string ToString() => $"{FirstName} {MiddleName} {LastName}"; }
در این کلاس، دو سازنده وجود دارند که یکی MiddleName را دریافت میکند و دیگری خیر. در اینجا کامپایلر تشخیص دادهاست که چون در سازندهی اولی که MiddleName را دریافت نمیکند، مقدار پیشفرض خاصیت MiddleName، نال خواهد بود و همچنین ما NullableContextOptions را نیز فعال کردهایم، بنابراین این خاصیت دیگر به صورت معمول و متداول یک نوع ارجاعی نالپذیر عمل نمیکند و دیگر نمیتوان نال را به عنوان مقدار پیشفرض آن، به آن نسبت داد. به همین جهت اخطار فوق ظاهر شدهاست.
برای رفع این مشکل:
به کامپایلر اعلام میکنیم: «میدانیم که MiddleName میتواند نال هم باشد» و آنرا در این زمینه راهنمایی میکنیم:
public string? MiddleName { get; set; }
public static class NullableReferenceTypes { //#nullable enable // Toggle to enable public static string Exemplify() { var vahid = new Person("Vahid", "N"); var length = GetLengthOfMiddleName(vahid); return $"{vahid.FirstName}'s middle name has {length} characters in it."; static int GetLengthOfMiddleName(Person person) { string middleName = person.MiddleName; return middleName.Length; } } }
در اینجا در متد محلی (local function) تعریف شده، سعی در دسترسی به خاصیت MiddleName وجود دارد و اکنون با تغییر جدیدی که اعمال کردیم، به صورت نالپذیر تعریف شدهاست.
همچنین در سطر بعدی آن نیز نتیجهی نهایی middleName، مورد استفاده قرار گرفتهاست که آن نیز مشکلدار تشخیص داده شدهاست.
مشکل اولین سطر را به این صورت میتوانیم برطرف کنیم:
var middleName = person.MiddleName;
اما مشکل سطر دوم هنوز باقی است:
علت اینجا است که متغیر middleName نیز اکنون ممکن است مقدار نال را داشته باشد. برای رفع این مشکل میتوان از اپراتور .? استفاده کرد و سپس اگر مقدار نهایی این عبارت نال بود، مقدار صفر را بازگشت میدهیم:
static int GetLengthOfMiddleName(Person person) { var middleName = person.MiddleName; return middleName?.Length ?? 0; }
امکان خاموش و روشن کردن ویژگی نوعهای ارجاعی نالنپذیر به صورت موضعی
زمانیکه خاصیت NullableContextOptions را فعال میکنیم، بر روی کل پروژه تاثیر میگذارد. برای مثال اگر یک چنین قابلیتی را بر روی پروژههای قدیمی خود فعال کنید، با صدها اخطار مواجه خواهید شد. به همین جهت است که این ویژگی حتی با فعالسازی C# 8.0 و انتخاب آن، به صورت پیشفرض غیرفعال است. بنابراین برای اینکه بتوان پروژههای قدیمی را قدم به قدم و سر فرصت، «مقاومتر» کرد، میتوان تعیین کرد که کدام قسمت، تحت تاثیر این ویژگی قرار بگیرد و کدام قسمتها خیر:
public static class NullableReferenceTypes { #nullable disable // Toggle to enable
مجبور ساختن خود به «مقاوم سازی» برنامه
اگر NullableContextOptions را فعال کنید، کامپایلر صرفا یکسری اخطار را در مورد مشکلات احتمالی صادر میکند؛ اما برنامه هنوز کامپایل میشود. برای اینکه خود را مقید به «مقاوم سازی» برنامه کنیم، میتوانیم با فعالسازی ویژگی TreatWarningsAsErrors در فایل csprj، این اخطارها را تبدیل به خطای کامپایلر کرده و از کامپایل برنامه جلوگیری کنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> <LangVersion>8.0</LangVersion> <NullableContextOptions>enable</NullableContextOptions> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> </Project>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
آیا اگر برنامهای با C# 7.0 کامپایل شود، کتابخانههای تهیه شدهی با C# 8.0 را میتواند استفاده کند؟
پاسخ: بله. از دیدگاه برنامههای قدیمی، کتابخانههای تهیه شدهی با C# 8.0، تفاوتی با سایر کتابخانه ندارند. آنها نوعهای نالپذیر جدید را مانند ?string مشاهده نمیکنند؛ آنها فقط string را مشاهده میکنند و روش کار کردن با آنها نیز همانند قبل است. بدیهی است در این حالت از مزایای کامپایلر C# 8.0 در تشخیص زود هنگام مشکلات برنامه محروم خواهند بود؛ اما عملکرد برنامه تفاوتی نمیکند.
وضعیت برنامهی C# 8.0 ای که از کتابخانههای C# 7.0 و یا قبل از آن استفاده میکند، چگونه خواهد بود؟
چون کتابخانههای قدیمیتر از مزایای کامپایلر C# 8.0 استفاده نمیکنند، خروجیهای آن بدون بروز خطایی توسط کامپایلر C# 8.0 استفاده میشوند؛ چون حجم اخطارهای صادر شدهی در این حالت بیش از حد خواهد بود. یعنی این بررسیهای کامپایلر صرفا برای کتابخانههای جدید فعال هستند و نه برای کتابخانههای قدیمی.
مهارتهای مواجه شدن با اخطارهای ناشی از فعالسازی NullableContextOptions
در مثالی که بررسی شد، یک نمونه از روشهای مواجه شدن با اخطارهای ناشی از فعالسازی ویژگی نوعهای ارجاعی نالنپذیر را بررسی کردیم. در ادامه روشهای تکمیلی دیگری را بررسی میکنیم.
1- هرجائیکه قرار است متغیر ارجاعی نالپذیر باشد، آنرا صراحتا اعلام کنید.
string name = null; // ERROR string? name = null; // OK!
نمونهی دیگر آن مثال زیر است:
public class Person { public Address? Address { get; set; }; public string Country => Address?.Country; // ERROR! }
public class Person { public Address? Address { get; set; }; public string? Country => Address?.Country; // OK! }
البته در این حالت باید به مثال زیر دقت داشت:
var node = this; // Initialize non-nullable variable while (node != null) { node = null; // ERROR! }
Node? node = this; // Initialize nullable variable while (node != null) { node = null; // OK! }
2- نوعهای خود را مقدار دهی اولیه کنید.
در مثال زیر:
public class Person { public string Name { get; set; } // ERROR! }
public class Person { public string? Name { get; set; } }
یا یک استثناء را صادر کنید:
public class Person { public string Name { get; set; } public Person(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); } }
البته در این حالت برای مقدار دهی اولیهی Name، حتما نیاز به تعریف یک سازندهاست و در این حالت کدهایی را که از سازندهی پیشفرض استفاده کرده بودند (مانند new Person { Name = "Vahid" })، باید تغییر دهید.
راهحل دیگر، مقدار دهی اولیهی این خواص بدون تعریف یک سازندهی اضافی است:
public class Person { public string Name { get; set; } = string.Empty; // -or- public string Name { get; set; } = ""; }
String.Empty Array.Empty<T>() Enumerable.Empty<T>()
public class Person { public Address Address { get; set; } = new Address(); }
اشتراکها
NET Core 3.1 Preview 2. منتشر شد
روشهای زیادی برای بازگشت چندین مقدار از یک متد وجود دارند؛ مانند استفادهی از آرایهها برای بازگشت اشیایی از یک جنس، ایجاد یک کلاس سفارشی با خواص متفاوت و استفاده از پارامترهای out و ref همانند روشهای متداول در C و ++C. در این بین روش دیگری نیز به نام Tuples از زمان NET 4.0. برای بازگشت چندین شیء با نوعهای مختلف، ارائه شدهاست که در C# 7 نحوهی تعریف و استفادهی از آنها بهبود قابل ملاحظهای یافتهاست.
Tuple چیست؟
هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهلهای از این کلاس جدید میباشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
و سپس، وهله سازی و بازگشت آن:
اما توسط Tuples، بدون نیاز به تعریف یک کلاس جدید، باز هم میتوان به همین دو خروجی، دسترسی یافت:
مشکلات نوع Tuple در نگارشهای قبلی دات نت
هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات میباشند:
الف) پارامترهای خروجی آنها ثابت و با نامهایی مانند Item1، Item2 و امثال آن هستند که در حین استفاده، به علت ضعف نامگذاری، کاربرد آنها دقیقا مشخص نیست و کاملا بیمعنا هستند:
ب) Reference Type هستند (کلاس هستند) و در زمان وهله سازی، میزان مصرف حافظهی بیشتری را نسبت به Value Types (معادل Tuples در C# 7) دارند.
ج) Tuples در دات نت 4، صرفا یک کتابخانهی اضافه شدهی به فریم ورک بوده و زبانهای دات نتی، پشتیبانی توکاری را از آنها جهت بهبود و یا ساده سازی تعریف آنها، ارائه نمیدهند.
ایجاد Tuples در C# 7
برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده میشود.
در مثال فوق، یک Tuple ایجاد شدهاست و در آن مقدار 3 به x1 و مقدار "one" به s1 انتساب داده شدهاند. به این عملیات deconstruction هم میگویند.
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.
اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
برای رفع این مشکل نیاز است بستهی نیوگت ذیل را نیز نصب کرد:
تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم میتوانند صورت گیرند:
بازگشت Tuples از متدها
متد ذیل، دو خروجی نتیجه و باقیماندهی تقسیم دو عدد صحیح را باز میگرداند:
برای این منظور، نوع خروجی متد به صورت (int, int) و همچنین مقدار بازگشتی نیز به صورت یک Tuple از نتیجه و باقیماندهی تقسیم، تعریف شدهاست.
در ادامه نحوهی استفادهی از این متد را مشاهده میکنید:
در اینجا امکان استفادهی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آنها را بر اساس نوع خروجی tuple مشخص میکند:
و یا حتی چون نوع var پارامترها در اینجا یکی است و در هر دو حالت به int اشاره میکند، میتوان این var را در خارج از پرانتز هم قرار داد:
و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل میتوان در C# 7 بازنویسی کرد:
و سپس به نحو واضحتری از آن استفاده نمود؛ بدون استفادهی اجباری از Item1 و غیره (هرچند هنوز هم میتوان از آنها استفاده کرد):
پشت صحنهی Tuples در C# 7
همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بستهی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر میکند:
برای مثال قطعه کد
توسط کامپایلر به قطعه کد ذیل ترجمه میشود:
- برخلاف نگارشهای پیشین دات نت که Tuples در آنها reference type بودند، این ValueTuple یک struct است و به همین جهت سربار تخصیص حافظهی کمتری را به همراه داشته و از لحاظ کارآیی و میزان مصرف حافظه بهینهتر عمل میکند.
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شدهی در یک Tuple وجود ندارد.
در اینجا هم مانند قبل (دات نت 4) 8 آیتم را میتوان تعریف کرد؛ اما چون آخرین آیتم ValueTuple تعریف شده نیز یک Tuple است، در عمل محدودیتی از نظر تعداد پارامتر نخواهیم داشت.
مفهوم Tuple Literals
همانند نگارشهای پیشین دات نت، خروجی یک Tuple را میتوان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
در این حالت برای دسترسی به مقادیر Tuple همانند قبل باید از فیلدهای Item1 و Item2 و ... استفاده کرد.
به علاوه در سی شارپ 7 میتوان برای اعضای یک Tuple نام نیز تعریف کرد که به آنها Tuple literals گویند:
در این حالت زمانیکه Tuple به یک متغیر از نوع var نسبت داده میشود، میتوان به خروجی آن بر اساس نامهای اعضای Tuple، بجای ذکر Item1 و ... دسترسی یافت که خوانایی بیشتری دارند.
و یا هنگام تعریف نوع خروجی، میتوان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم میگویند:
و نمونهای از کاربرد آن به صورت ذیل است که در اینجا خروجی Tuple صرفا به یک متغیر از نوع var نسبت داده شدهاست و توسط نام پارامترهای خروجی متد، میتوان به اعضای Tuple دسترسی یافت.
مفهوم Deconstructing Tuples
مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 میتوان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
- در اینجا یک متد جدید را به نام Deconstruct مشاهده میکنید. کار این متد جدید که توسط کامپایلر استفاده خواهد شد، ارائهی روشی است برای «تجزیهی» یک نوع، به یک Tuple. متد Deconstruct تعریف شدهی در اینجا توسط پارامترهایی از نوع out، دو خروجی را مشخص میکنند. امکان تعریف این متد ویژه، به صورتیکه یک Tuple را بازگرداند، وجود ندارد.
- علت تعریف این دو خروجی هم به constructor و یا سازندهی کلاس بر میگردد که دو ورودی را دریافت میکند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد میتوان متد Deconstruct تعریف کرد؛ به همراه خروجیهایی متناظر با نوع پارامترهای سازندهها.
- علت استفادهی از نوع خروجی out نیز این است که در #C نمیتوان چندین overload را صرفا بر اساس نوع خروجیهای متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیهی یک شیء به یک tuple فراخوانی میشود. در مثال زیر، شیء p1 به یک Tuple تجزیه شدهاست و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا میکند:
امکان تعریف متد Deconstruct، به صورت یک متد الحاقی
روش اول تعریف متد ویژهی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفادهی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
در اینجا کلاس مستطیل دارای سازندهای با دو پارامتر است؛ اما متد Deconstruct آن به صورت یک متد الحاقی، خارج از کلاس اصلی تعریف شدهاست.
اکنون امکان انتساب وهلهای از این کلاس به یک Tuple وجود دارد:
امکان جایگزین کردن Anonymous types با Tuples
قطعه کد ذیل را در نظر بگیرید:
در اینجا خروجی LINQ تهیه شده یک لیست anonymously typed است؛ با محدودیتهایی مانند عدم امکان استفادهی از خروجی آن در سایر اسمبلیها. این نوعهای ویژه تنها محدود هستند به همان اسمبلی که در آن تعریف میشوند. اما در C# 7 میتوان قطعه کد فوق را با Tuples به صورت ذیل بازنویسی کرد که این محدودیتها را هم ندارد (با هدف به حداقل رساندن تعداد ViewModelهای تعریفی یک برنامه):
سایر کاربردهای Tuples
از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمیشود. در ذیل نحوهی استفادهی از آنها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفادهی از آنها را در آرگومان جنریک یک متد async هم مشاهده میکنید:
Tuple چیست؟
هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهلهای از این کلاس جدید میباشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
public class Location { public string City { get; set; } public string State { get; set; } public Location(string city, string state) { City = city; State = state; } }
var location = new Location("Lake Charles","LA");
var location = new Tuple<string,string>("Lake Charles","LA"); // Print out the address var address = $"{location.Item1}, {location.Item2}";
مشکلات نوع Tuple در نگارشهای قبلی دات نت
هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات میباشند:
static Tuple<int, string, string> GetHumanData() { return Tuple.Create(10, "Marcus", "Miller"); }
var data = GetHumanData(); Console.WriteLine("What is this value {0} or this {1}", data.Item1, data.Item3);
ج) Tuples در دات نت 4، صرفا یک کتابخانهی اضافه شدهی به فریم ورک بوده و زبانهای دات نتی، پشتیبانی توکاری را از آنها جهت بهبود و یا ساده سازی تعریف آنها، ارائه نمیدهند.
ایجاد Tuples در C# 7
برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده میشود.
(int x1, string s1) = (3, "one"); Console.WriteLine($"{x1} {s1}");
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.
اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
PM> install-package System.ValueTuple
تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم میتوانند صورت گیرند:
int x2; string s2; (x2, s2) = (42, "two"); Console.WriteLine($"{x2} {s2}");
بازگشت Tuples از متدها
متد ذیل، دو خروجی نتیجه و باقیماندهی تقسیم دو عدد صحیح را باز میگرداند:
static (int, int) Divide(int x, int y) { int result = x / y; int reminder = x % y; return (result, reminder); }
در ادامه نحوهی استفادهی از این متد را مشاهده میکنید:
(int result, int reminder) = Divide(11, 3); Console.WriteLine($"{result} {reminder}");
در اینجا امکان استفادهی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آنها را بر اساس نوع خروجی tuple مشخص میکند:
(var result1, var reminder1) = Divide(11, 3); Console.WriteLine($"{result1} {reminder1}");
var (result1, reminder1) = Divide(11, 3);
و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل میتوان در C# 7 بازنویسی کرد:
static (int, string, string) GetHumanData() { return (10, "Marcus", "Miller"); }
(int Age, string FirstName, string LastName) results = GetHumanData(); Console.WriteLine(results.Age); Console.WriteLine(results.FirstName); Console.WriteLine(results.LastName);
پشت صحنهی Tuples در C# 7
همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بستهی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر میکند:
ValueTuple<int, int> tuple1 = Divide(11, 3);
(int, int) n = (1,1); System.Console.WriteLine(n.Item1);
ValueTuple<int, int> n = new ValueTuple<int, int>(1, 1); System.Console.WriteLine(n.Item1);
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شدهی در یک Tuple وجود ندارد.
(int,int,int,int,int,int,int,(int,int))
مفهوم Tuple Literals
همانند نگارشهای پیشین دات نت، خروجی یک Tuple را میتوان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
var tuple2 = ("Stephanie", 7); Console.WriteLine($"{tuple2.Item1}, {tuple2.Item2}");
به علاوه در سی شارپ 7 میتوان برای اعضای یک Tuple نام نیز تعریف کرد که به آنها Tuple literals گویند:
var tuple3 = (Name: "Matthias", Age: 6); Console.WriteLine($"{tuple3.Name} {tuple3.Age}");
و یا هنگام تعریف نوع خروجی، میتوان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم میگویند:
static (int radius, double area) CalculateAreaOfCircle(int radius) { return (radius, Math.PI * Math.Pow(radius, 2)); }
var circle = CalculateAreaOfCircle(2); Console.WriteLine($"A circle of radius, {circle.radius}," + $" has an area of {circle.area:N2}.");
مفهوم Deconstructing Tuples
مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 میتوان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
class Person { private readonly string _firstName; private readonly string _lastName; public Person(string firstname, string lastname) { _firstName = firstname; _lastName = lastname; } public override String ToString() => $"{_firstName} {_lastName}"; public void Deconstruct(out string firstname, out string lastname) { firstname = _firstName; lastname = _lastName; } }
- علت تعریف این دو خروجی هم به constructor و یا سازندهی کلاس بر میگردد که دو ورودی را دریافت میکند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد میتوان متد Deconstruct تعریف کرد؛ به همراه خروجیهایی متناظر با نوع پارامترهای سازندهها.
- علت استفادهی از نوع خروجی out نیز این است که در #C نمیتوان چندین overload را صرفا بر اساس نوع خروجیهای متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیهی یک شیء به یک tuple فراخوانی میشود. در مثال زیر، شیء p1 به یک Tuple تجزیه شدهاست و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا میکند:
var p1 = new Person("Katharina", "Nagel"); (string first, string last) = p1; Console.WriteLine($"{first} {last}");
امکان تعریف متد Deconstruct، به صورت یک متد الحاقی
روش اول تعریف متد ویژهی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفادهی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
public class Rectangle { public Rectangle(int height, int width) { Height = height; Width = width; } public int Width { get; } public int Height { get; } } public static class RectangleExtensions { public static void Deconstruct(this Rectangle rectangle, out int height, out int width) { height = rectangle.Height; width = rectangle.Width; } }
اکنون امکان انتساب وهلهای از این کلاس به یک Tuple وجود دارد:
var r1 = new Rectangle(100, 200); (int height, int width) = r1; Console.WriteLine($"height: {height}, width: {width}");
امکان جایگزین کردن Anonymous types با Tuples
قطعه کد ذیل را در نظر بگیرید:
List<Employee> allEmployees = new List<Employee>() { new Employee { ID = 1L, Name = "Fred", Salary = 50000M }, new Employee { ID = 2L, Name = "Sally", Salary = 60000M }, new Employee { ID = 3L, Name = "George", Salary = 70000M } }; var wellPaid = from oneEmployee in allEmployees where oneEmployee.Salary > 50000M select new { EmpName = oneEmployee.Name, Income = oneEmployee.Salary };
var wellPaid = from oneEmployee in allEmployees where oneEmployee.Salary > 50000M orderby oneEmployee.Salary descending select (EmpName: oneEmployee.Name, Income: oneEmployee.Salary); var highestPaid = wellPaid.First().EmpName;
سایر کاربردهای Tuples
از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمیشود. در ذیل نحوهی استفادهی از آنها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفادهی از آنها را در آرگومان جنریک یک متد async هم مشاهده میکنید:
public Task<(int index, T item)> FindAsync<T>(IEnumerable<T> input, Predicate<T> match) { var dictionary = new Dictionary<(int, int), string>(); throw new NotSupportedException(); }
اشتراکها
jQuery 3.5.0 منتشر شد
The main change in this release is a security fix, and it’s possible you
will need to change your own code to adapt. Here’s why: jQuery used a
regex in its jQuery.htmlPrefilter
method to ensure that all closing tags were XHTML-compliant when passed
to methods. For example, this prefilter ensured that a call like jQuery("<div class='hot' />")
is actually converted to jQuery("<div class='hot'></div>")
. Recently, an issue was reported that demonstrated the regex could introduce a cross-site scripting (XSS) vulnerability.
اشتراکها
مقایسه عملکرد HTTP/3 با HTTP/2
برای استفاده سادهتر از ابزارهای unit testing در ویژوال استودیو افزونههای زیادی وجود دارند، از ری شارپر تا CodeRush تا حتی امکانات نسخهی کامل VS.NET که با MSTest یکپارچه است. اما اگر نخواهیم از MSTest استفاده کنیم و همچنین افزونهها را هم بخواهیم حذف کنیم (مثلا از نسخهی رایگان express استفاده کنیم)، چطور؟
برای حل این مشکل چندین روش وجود دارد. یا میشود از test runner اینها استفاده کرد که اصلا نیازی به IDE ندارند و مستقل است؛ یا میتوان به صورت زیر هم عمل کرد:
به خواص پروژه در VS.NET مراجعه کنید. برگهی Build events را باز کنید. در اینجا میخواهیم post-build event را مقدار دهی کنیم. به این معنا که پس از هر build موفق، لطفا این دستورات خط فرمان را اجرا کن.
NUnit به همراه test runner خط فرمان هم ارائه میشود و نام آن nunit-console.exe است. اگر به محل نصب آن مراجعه کنید، عموما در آدرس C:\Program Files\NUnit xyz\bin\nunit-console.exe قرار دارد. برای استفاده از آن تنها کافی است تنظیم زیر صورت گیرد:
c:\path\nunit-console.exe /nologo $(TargetPath)
TargetPath به صورت خودکار با نام اسمبلی جاری پروژه در زمان اجرا جایگزین میشود.
اکنون پس از هر Build، به صورت خودکار nunit-console.exe اجرا شده، اسمبلی برنامه که حاوی آزمونهای واحد است به آن ارسال گردیده و سپس خروجی کار در output window نمایش داده میشود. اگر خطایی هم رخ داده باشد در قسمت errors قابل مشاهده خواهد بود.
در اینجا حتی بجای برنامه کنسول یاده شده میتوان از برنامه nunit.exe هم استفاده کرد. در این حالت GUI اصلی پس از هر Build نمایش داده میشود:
c:\path\nunit.exe $(TargetPath)
چند نکته:
1- برنامه nunit-console.exe چون در حال حاضر برای دات نت 2 کامپایل شده امکان بارگذاری dll های دات نت 4 را ندارد. به همین منظور فایل nunit-console.exe.config را باز کرده و تنظیمات زیر را به آن اعمال کنید:
<configuration>
<startup>
<supportedRuntime version="v4.0.30319" />
</startup>
و همچنین:
<runtime>
<loadFromRemoteSources enabled="true" />
2- خروجی نتایج اجرای آزمونها را به صورت XML هم میتوان ذخیره کرد. مثلا:
c:\path\nunit-console.exe /xml:$(ProjectName)-tests.xml /nologo $(TargetPath)
3- از فایل xml ذکر شده میتوان گزارشات زیبایی تهیه کرد. برای مثال:
Generating Report for NUnit
NUnit2Report Task
جهت مطالعه بیشتر:
Setting up Visual C#2010 Express with NUnit
Use Visual Studio's Post-Build Events to Automate Unit Testing Running
3 Ways to Run NUnit From Visual Studio
اشتراکها
مصاحبه با خالق ++C
مطالبی که در این ویدئو به آن پرداخته میشود :
What is the keynote about? How do we write modern C++ code? Guideline support library and Static analysis Call to action for the C++ community! Enhancing productivity by eliminating whole classes of bugs Extending the C++ core guidelines What do you expect from these static analysis checkers? How can I get started?