C# for Beginners | Full 2-hour course - YouTube
00:00:00 – Start
00:00:09 – What is C#
00:01:27 – C#: Hello World
00:06:01 – C#: The Basics of Strings
00:14:35 – C#: Searching Strings
00:17:27 – C#: Numbers and Integer Math
00:22:04 – C#: Numbers and Integer Precision
00:27:32 – C#: Numbers and Decimals
00:33:10 – C#: Branches (if)
00:44:29 – C#: “Hello World” Explained
00:50:29 – C#: What are Loops?
00:57:42 – C#: Combining Branches and Loops
01:04:34 – C#: Arrays, List, and Collections
01:15:09 – C#: Sort, Search, and Index Lists
01:22:08 – C#: Lists of Other Types
01:29:51 – C#: Debugging
01:34:30 – C#: Object Oriented Programming – Objects and Classes
01:47:20 – C#: Object Oriented Programming – Methods and Members
01:56:02 – C#: Object Oriented Programming – Methods and Exceptions
02:03:00 – C#: Object Oriented Programming – Catching Exceptions
استفاده از XQuery - قسمت اول
XQuery پیاده سازی شده در SQL Server با استانداردهای XQuery 1.0 و XPath 2.0 سازگار است. XQuery برای کار با نودهای مختلف یک سند XML، از XPath استفاده میکند. همچنین باید دقت داشت که این زبان به بزرگی و کوچکی حروف حساس است. در آن تمام واژههای کلیدی lowercase هستند و تمام متغیرها با علامت $ شروع میشوند.
ورودی و خروجی در XQuery
استاندارد XQuery از یک سری توابع ورودی مانند doc برای کار با یک سند و collection برای پردازش چندین سند کمک میگیرد. SQL Server از هیچکدام از این توابع پشتیبانی نمیکند. در اینجا از XQuery، به کمک متدهای نوع دادهای XML استفاده خواهد شد. این متدها شامل موارد ذیل هستند:
- query : یک xml را به عنوان ورودی گرفته و نهایتا یک خروجی XML دیگر را بر میگرداند.
- exist : خروجی bit دارد؛ true یا false.
- value : یک خروجی SQL Type را ارائه میدهد.
- nodes : خروجی جدولی دارد.
- modify : برای تغییر اطلاعات بکار میرود.
این موارد را در طی مثالهایی بررسی خواهیم کرد. بنابراین در ادامه نیاز است یک سند XML را که در طی مثالهای این قسمت مورد استفاده قرار خواهد گرفت، به شرح ذیل مدنظر داشته باشیم:
DECLARE @data XML SET @data = '<people> <person> <name> <givenName>name1</givenName> <familyName>lname1</familyName> </name> <age>33</age> <height>short</height> </person> <person> <name> <givenName>name2</givenName> <familyName>lname2</familyName> </name> <age>40</age> <height>short</height> </person> <person> <name> <givenName>name3</givenName> <familyName>lname3</familyName> </name> <age>30</age> <height>medium</height> </person> </people>'
همانطور که در قسمت قبل نیز ذکر شد، اگر اطلاعات شما در یک فایل XML قرار دارند، نحوهی خواندن آن به شکل یک فیلد XML با کمک openrowset مطابق دستورات زیر خواهد بود:
declare @data xml set @data = (select * from openrowset(bulk 'c:\path\data.xml', single_blob) as x)
بررسی متد query
متد query یک XQuery متنی را دریافت کرده، آنرا بر روی XML ورودی اجرا نموده و سپس یک خروجی XML دیگر را ارائه خواهد داد.
اگر به کتابهای استاندارد XQuery مراجعه کنید، به یک چنین کوئریهایی خواهید رسید:
for $p in doc("data.xml")/people/person where $p/age > 30 return $p/name/givenName/text()
SELECT @data.query(' for $p in /people/person where $p/age > 30 return $p/name/givenName/text() ')
بررسی متد value
در ادامه متد value را بررسی خواهیم کرد. در اینجا قصد داریم مقدار سن اولین شخص را نمایش دهیم:
SELECT @data.value('/people/person/age', 'int')
اگر کوئری فوق را اجرا کنیم با خطای ذیل مواجه خواهیم شد:
XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
برای رفع این مشکل و اشاره به اولین شخص، میتوان از روش ذیل استفاده کرد:
SELECT @data.value('(/people/person/age)[1]', 'int')
تولید schema برای سند XML بحث جاری
با استفاده از برنامه Infer.exe مایکروسافت به سادگی میتوان برای یک سند XML، فایل Schema ایجاد کرد. این برنامه را از اینجا میتوانید دریافت کنید. پس از آن، اگر فرض کنیم اطلاعات سند XML مثال فوق در فایلی به نام people.xml ذخیره شدهاست، میتوان schema آنرا توسط دستور ذیل تولید کرد:
Infer.exe people.xml -o schema.xsd
که نهایتا چنین شکلی را خواهد داشت:
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="people"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" name="person"> <xs:complexType> <xs:sequence> <xs:element name="name"> <xs:complexType> <xs:sequence> <xs:element name="givenName" type="xs:string" /> <xs:element name="familyName" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="age" type="xs:unsignedByte" /> <xs:element name="height" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
این خروجی را که اکنون به صورت یک فایل xsd، در کنار فایل xml معرفی شده به آن میتوان یافت، با استفاده از openrowset قابل بارگذاری است:
declare @schema xml set @schema = (select * from openrowset(bulk 'c:\path\schema_1.xsd', single_blob) as x)
سپس از این متغیر برای تعریف یک اسکیما کالکشن جدید استفاده خواهیم کرد:
CREATE XML SCHEMA COLLECTION poeple_xsd AS @schema
DECLARE @data XML(poeple_xsd) SET @data = 'مانند قبل با همان محتوایی که در ابتدای بحث عنوان شد'
SELECT @data.value('/people/person[1]/age', 'int')
XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xs:unsignedByte *'
مشکل کوئری نوشته در اینجا این است که زمانیکه نوع XML تعریف میشود، پیش فرض آن content است. یعنی در این حالت چندین root elemnt مجاز هستند. بنابراین person 1 درخواستی، میتواند چندین خروجی داشته باشد که در متد value مجاز نیست. این متد، پیش از اجرای کوئری، توسط parser تعیین اعتبار میشود و الزاما نیازی نیست تا حتما اجرا شده و سپس مشخص شود که چندین خروجی حاصل آن است.
اینبار تنها کاری که باید برای رفع مشکل گزارش شده انجام شود، تغییر content پیش فرض به document است:
DECLARE @data XML(document poeple_xsd)
sequences در XQuery
Sequences بسیار شبیه به آرایهای از آیتمها هستند و منظور مجموعهای از نودها یا مقادیر آنها است. برای مثال به ورودی کوئریهای XQuery به شکل توالی از یک سند و به خروجی آنها همانند توالی صفر تا چند نود نگاه کنید.
DECLARE @x XML SET @x='' SELECT @x.query( ' 1,2 (: 1,2 :) ')
همچنین باید دقت داشت که این توالی خطی تفسیر میشود.
DECLARE @x XML SET @x='' SELECT @x.query( ' for $x in (1,2,3) for $y in (4,5) return ($x,$y) ')
به علاوه در SQL Server امکان تعریف Heterogeneous sequences وجود ندارد؛ به عبارتی توالی بین مقادیر و نودها مجاز نیست. برای مثال اگر کوئری زیر را اجرا کنید:
DECLARE @x XML SET @x='' SELECT @x.query( ' 1, <node/> ')
XQuery [query()]: Heterogeneous sequences are not allowed: found 'xs:integer' and 'element(node,xdt:untyped)'
این پنل به سه بخش اصلی تقسیم میشود :
- بخش اصلی یا NodeView که محتوای صفحه را بصورت درختی و مرتب و رنگی نمایش میدهد.
- Panel Toolbar که در بالای پنل قرار دارد.
- Side Panels که شامل پنلهای Style , Computed , Layout , DOM میشود.
که به ترتیب برای نمایش و ویرایش استایلها ، مشاهده استایلهای محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView است.
در این مقاله با دو بخش NodeView و Panel Toolbar ، و در مقالهی بعد با پنلهای سمت راست یعنی Side Panels آشنا میشویم.
ویرایش HTML
برای اضافه کردن یک Attribute جدید به تگ هم بروی تگ مورد نظر راست کلیک میکنید و سپس گزینهی New Attribute را انتخاب میکنید. ابتدا نام ویژگی ، یک Enter ، سپس مقدار را وارد میکنید. با زدن کلیدهای Enter متوالی ، میتوانید به وارد کردن ویژگیها ادامه دهید.
- Break On Mutate
این دکمه امکان توقف کد JavaScript ای که سعی در ویرایش محتوای صفحه را دارد ، میدهد.
زمانی که FireBug تشخیص دهد که کدی سعی در ویرایش محتوا دارد ، شما را به خط مورد نظر از کد ، در پنل Script منتقل میکند.
- Edit
این دکمه برای ویرایش مستقیم محتوای یک تگ بکار میرود
نکتهی جالب در ویرایش محتوا در فایرباگ این است که تغییرات در لحظه اعمال میشوند و نیاز به عمل بروزرسانیِ جداگانه نیست. برای مثال در همین قسمت Edit ، با هر ویرایش محتوا ، تغییرات در لحظه اعمال میشوند.
- Element Path
زمانی که یک تگ را در FireBug انتخاب میکنید ، لیستی از تگها که از تگ جاری شروع و به تگ ریشه ختم میشود ، نمایش داده میشود که با کلیک بروی هرکدام ، همان به عنوان تگ فعلی یا انتخاب شده تعیین میشود.
نتیجهی عملیاتی که بروی این تگهای انجام میدهید (حرکت موس و راست کلیک کردن) معادل همان عملیات بروی تگهای نمایش داده شده در قسمت اصلی (NodeView) است.
Options Menu
- Show Full Text
در صورت فعال بودن ، متون بصورت کامل نمایش داده میشوند ، در غیراینصورت بصورت خلاصه شده نمایش داده میشوند.
- Show White Space
در صورت فعال بودن ، فضاهای خالی ، کرکترهای خط جدید و ... را نمایش میدهد.
- Show Comments
در صورت فعال بودن ، کامنتها را هم نمایش میدهد
- سه گزینه ی Show Entities As Symbols ، Show Entities As Names و Show Entities As Unicode ، نوع نمایش کرکترهای ویژه را تعیین میکنند.
- Highlight Changes
در صورت فعال بودن ، تگ تغییر یافته توسط JavaScript (یا تگ والدی که قابل مشاهده باشد) Highlight میشود
- Expand Changes
در صورت فعال بودن ، زمان تغییر دادن یک تگ توسط JavaScript ، والدهای آن تگ باز (Expand) میشوند
- Scroll Changes Into View
در صورت فعال بودن این گزینه ، هنگام تغییر یک تگ در صفحه توسط JavaScript ، قسمت NodeView به قسمت تغییر بافته حرکت میکند.
(فعال بودن این گزینه هنگام بررسی سایت هایی که اسلایدر یا سیستم هایی مشابه دارند ، باعث میشه که نتوانید بروی قسمتهای سایت تمرکز کنید و مدام اسکرول به قسمت تغییرات منتقل میشود.)
- Shade Box Model
در صورت فعال بودن ، فضای margin , padding و content را به شکلی که در بالا گفته شد نمایش میدهد ، در غیر اینصورت فقط یک خط دور تگ نشان میدهد.
- Show Quick Info Box
در صورت فعال بودن ، یک پاپآپ به همراه اطلاعات مختصری از تگ در صفحه نمایش میدهد.
Context Menu
- Copy HTML
خود تگ و محتوایش را بصورت HTML در حافظه کپی میکند.
- Copy innerHTML
محتوای تگ را در حافظه کپی میکند.
- Copy XPath
آدرس XPath تگ را در حافظه کپی میکند.
- Copy CSS Path
CSS Selector تگ را در حافظه کپی میکند.
- Log Events
رویدادهای تگ را در پنل Console ثبت میکند. (کلیک موس ، فشردن کلید ، ...)
برای لغو لاگ کردن ، مجددا بروی تگ راست کلیک کرده و این گزینه را از حالت انتخاب خارج کنید.
- Scroll Into View
صفحه را به جایی که تگ قابل نمایش است منتقل میکند.
- New Attribute...
یک attribute جدید به تگ اضافه میکند.
برای لغو عملیات ، دکمهی Esc را بزنید.
- Edit Attribute "<attribute name>"...
اگر بروی یک attribute راست کلیک کرده باشید ، این گزینه و گزینهی بعدی را مشاهده خواهید کرد.
معادل کلیک بروی نام attribute است ، نام را به حالت ویرایش درمی آورد.
- Delete Attribute "<attribute name>"
attribute را حذف میکند.
- Edit HTML...
تگ را به حالت ویرایش میبرد.
معادل انتخاب تگ ، زدن کلید Edit است.
- Delete Element
تگ را حذف میکند.
راه دیگر حذف یک تگ ، انتخاب تگ و فشردن کلید Del از کیبورد است.
- Expand/Contract All
تگ و Childهایش را باز/بسته میکند. (بجز تگ های <script> , <style> , <link>)
میتوان با ترکیب کلیدShift + * هم این کار را انجام داد که در این حالت تگهای فوق هم شامل باز/بسته شدن میشوند.
- Break On Attribute Change
مانع اجرای کد JavaScript ای که attribute تگ را تغییر میدهد میشود و فایرباگ به خطی که باعث این عمل شده است در پنل Script منتقل میشود.
به عبارتی دیگر یک Break Point در خط JavaScript ای که باعث ویرایش attribute میشود قرار میدهد.
- Break On Child Addition or Removal
مشابه توضیح قبل ، Break Point را در خطی که باعث اضافه/حذف شدن تگِ Child شده است قرار میدهد.
- Break On Element Removal
مشابه توضیح قبل ، Break Point را در خطی که باعث حذف شدن تگ شده است قرار میدهد.
- Inspect in DOM Tab
تگ فعلی را در پنل DOM ، برای بررسی باز میکند.
«Splashdata، توسعه دهنده نرم افزارهای امنیتی، فهرست سالانه خود را از رایجترین رمزهای عبور منتشر کرده است.»
میشود از این لیست برای بهبود پروسه ثبت نام در یک سایت استفاده کرد و همان زمان که کاربر کلمه عبور ضعیفی را وارد کرده است، به او پیغام داد که «کلمه عبور وارد شده را راحت میتوان حدس زد!»
using System.Linq; namespace SecurityModule { public static class SafePassword { public static ISet<string> BadPasswords = new HashSet<string> { "password", "password1", "123456", "12345678", "1234", "qwerty", "12345", "dragon", "******", "baseball", "football", "letmein", "monkey", "696969", "abc123", "mustang", "michael", "shadow", "master", "jennifer", "111111", "2000", "jordan", "superman", "harley", "1234567", "iloveyou", "trustno1", "sunshine", "123123", "welcome" }; public static bool IsSafePasword(this string data) { if (string.IsNullOrWhiteSpace(data)) return false; if (data.Length < 5) return false; if (BadPasswords.Contains(data.ToLowerInvariant())) return false; if (data.AreAllCharsEuqal()) return false; return true; } public static bool AreAllCharsEuqal(this string data) { if (string.IsNullOrWhiteSpace(data)) return false; data = data.ToLowerInvariant(); var firstElement = data.ElementAt(0); var euqalCharsLen = data.ToCharArray().Count(x => x == firstElement); if (euqalCharsLen == data.Length) return true; return false; } } }
- خالی نیست.
- بیشتر از 5 کاراکتر طول دارد.
- تمام حروف بکارگرفته شده در آن یکسان نیستند.
و در ASP.NET MVC با استفاده از قابلیت Remote validation آن استفاده از این متد به نحو زیر خواهد بود:
public partial class RegisterController : Controller { //... [HttpPost] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public virtual ActionResult CheckPassword(string password1) { return Json(password1.IsSafePasword()); } }
سپس قسمتی از ViewModel متناظر با صفحه ثبت نام سایت، به شکل زیر اضافه و تعریف میگردد:
using System.ComponentModel.DataAnnotations; using System.Web.Mvc; namespace MyBlog.Models { public class RegisterViewModel { //... [Display(Name = "کلمه عبور")] [Required(ErrorMessage = "لطفا کلمه عبور خود را وارد نمائید")] [DataType(DataType.Password)] [StringLength(50, MinimumLength = 5, ErrorMessage = "حداقل طول کلمه عبور 5 حرف است")] [Remote(action: "CheckPassword", controller: "Register", HttpMethod = "POST", ErrorMessage = "کلمه عبور وارد شده را راحت میتوان حدس زد!")] public string Password1 { get; set; } } }
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
String.format = function () { var s = arguments[0]; for (var arg in arguments) { var i = parseInt(arg); s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
console.log(String.format("{0} is nice!", "donettips.info"));
donettips.info is nice!
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
donettips.info is very nice! {0} is {1} nice!
String.format = function () { var original = arguments[0], replaced; for (var i = 0; i < arguments.length - 1; i++) { replaced = ''; while (replaced != original) { original = replaced || original; replaced = original.replace("{" + i + "}", arguments[i + 1]); } } return replaced; };
donettips.info is very nice! donettips.info is very nice!
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]); } return s; };
String.format = function () { var s = arguments[0], i = arguments.length - 1; while (i--) { s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]); } return s; };
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
zero:0 {2}:1 two:2
zero:0 two:1 two:2
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
zero:0 one:1 one:2
zero:0 one:1 {1}:2
String.format = function () { var args = arguments; return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; }); };
console.log(String.format("{0} is {1} nice!", "donettips.info"));
donettips.info is undefined nice!
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/{(\d+)}/g, function (match, number) { var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}", "zero", "{2}", "two"));
zero:0 {2}:1 two:2, {zero} {{{2}}} {{{two}}} two
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
zero:0 {2}:1 two:2, {0} {{2}} {{2}} two
String.prototype.format = function () { ... }
String.prototype.format = function () { var s = this.toString(), args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } return typeof args[number] != 'undefined' ? args[number] : match; }); };
console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}".format("zero", "{2}", "two"));
String.format = function () { var s = arguments[0], args = arguments[1]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
String.prototype.format = function () { var s = this.toString(), args = arguments[0]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" })); console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
String.format = function String$format(format, args) { /// <summary locid="M:J#String.format" /> /// <param name="format" type="String"></param> /// <param name="args" parameterArray="true" mayBeNull="true"></param> /// <returns type="String"></returns> // var e = Function._validateParams(arguments, [ // { name: "format", type: String }, // { name: "args", mayBeNull: true, parameterArray: true } // ]); // if (e) throw e; return String._toFormattedString(false, arguments); }; String._toFormattedString = function String$_toFormattedString(useLocale, args) { var result = ''; var format = args[0]; for (var i = 0; ; ) { var open = format.indexOf('{', i); var close = format.indexOf('}', i); if ((open < 0) && (close < 0)) { result += format.slice(i); break; } if ((close > 0) && ((close < open) || (open < 0))) { if (format.charAt(close + 1) !== '}') { throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); } result += format.slice(i, close + 1); i = close + 2; continue; } result += format.slice(i, open); i = open + 1; if (format.charAt(i) === '{') { result += '{'; i++; continue; } if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); var brace = format.substring(i, close); var colonIndex = brace.indexOf(':'); var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1; if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid); var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1); var arg = args[argNumber]; if (typeof (arg) === "undefined" || arg === null) { arg = ''; } if (arg.toFormattedString) { result += arg.toFormattedString(argFormat); } else if (useLocale && arg.localeFormat) { result += arg.localeFormat(argFormat); } else if (arg.format) { result += arg.format(argFormat); } else result += arg.toString(); i = close + 1; } return result; }
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001)); // result: 100.00, ¤100.00, 10,000.01 %, 100.0001 console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45))); // result: 02/01/2015, 10:45
var template = jQuery.validator.format("{0} is not a valid value"); console.log(template("abc")); // result: 'abc is not a valid value'
String.format([full format string], [arguments...]); // or: [date|number].format([partial format string]);
// Object path String.format("Welcome back, {username}!", { id: 3, username: "JohnDoe" }); // Result: "Welcome back, JohnDoe!" // Date/time formatting String.format("The time is now {0:t}.", new Date(2009, 5, 1, 13, 22)); // Result: "The time is now 01:22 PM." // Date/time formatting (without using a full format string) var d = new Date(); d.format("hh:mm:ss tt"); // Result: "02:28:06 PM" // Custom number format string String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111); // Result: "Please call me at +46 (0) 111-11 11." // Another custom number format string String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346); // Result: "The last year result was -$5,543.35." // Alignment String.format("|{0,10:PI=0.00}|", Math.PI); // Result: "| PI=3.14|" // Rounding String.format("1/3 ~ {0:0.00}", 1/3); // Result: "1/3 ~ 0.33" // Boolean values String.format("{0:true;;false}", 0); // Result: "false" // Explicitly specified localization // (note that you have to include the .js file for used cultures) msf.setCulture("en-US"); String.format("{0:#,0.0}", 3641.667); // Result: "3,641.7" msf.setCulture("sv-SE"); String.format("{0:#,0.0}", 3641.667); // Result: "3 641,7"
//inline arguments String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value'); //returns: 'some string with first value and second value injected argument {number}' //single array String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]); //returns: 'some string with first value and second value injected using array {number}' //single object String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'}); //returns: 'some string with first value and second value injected using {propertyName}'
ایجاد هوک سفارشی useClickOutside
برای این منظور فایل جدید src\components\useClickOutside.tsx را ایجاد کرده و به صورت زیر تکمیل میکنیم:
import { useEffect } from "react"; const useClickOutside = (ref, handler) => { useEffect(() => { const listener = (event) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [handler, ref]); }; export { useClickOutside };
- متد هوک سفارشی ما، دو پارامتر ref و handler را دریافت میکند. ref به DOM Element جاری اشاره میکند و handler تابعی است که هنگام کلیک در خارج از ناحیهی یک DOM Element خاص، اجرا میشود.
- سپس یک listener را تعریف کردهایم که این تابع handler را اجرا میکند؛ البته به شرطیکه DOM Element ارسالی وجود داشته باشد و خود target هم نباشد.
- در ادامه این listener را به رخدادهای mousedown و touchstart متصل کرده و پاکسازی آنها را هم در قسمت return متد useEffect انجام دادهایم.
- همچنین چون میخواهیم تنها در صورت تغییر پارامترهای ارسالی به هوک سفارشی جاری، این useEffect به روز رسانی شود، این پارامترها را در قسمت Dependency List مربوط به متد useEffect نیز ذکر کردهایم.
تا اینجا اگر کدهای فوق را دنبال کنید، چون پسوند این فایل tsx است، خطاهای تایپاسکریپتی زیر را مشاهده خواهید کرد که به دلیل انتساب ضمنی نوع any، به این پارامترهای بدون نوع است:
استفاده از هوک سفارشی useClickOutside
بنابراین قدم بعدی کار، تکمیل نوعهای مرتبط با این پارامترها است. برای این منظور، ابتدا سعی میکنیم تا این هوک را در کامپوننت src\components\ReducerButtons.tsx قسمت قبلی استفاده کنیم تا نسبت به نوع پارامترهای ارسالی به این هوک، درک بهتری را پیدا کنیم:
import { useClickOutside } from "./useClickOutside"; // ... export const ReducerButtons = () => { const [state, dispatch] = useReducer(reducer, initialState); const ref = useRef<HTMLDivElement>(null); useClickOutside(ref, () => { console.log("clicked outside"); }); return ( <div ref={ref}> // ... </div> ); };
- ابتدا importهای لازم را به ابتدای ماژول افزودهایم.
- سپس با استفاده از هوک useRef که در قسمت چهارم آنرا بررسی کردیم، ارجاعی را به المان div رندر شده، بدست آوردهایم.
- در آخر هوک سفارشی جدید useClickOutside را فراخوانی کردهایم که آرگومان اول آن به DOM Element مربوط به div اشاره میکند و پارامتر دوم آن، تابعی است که پس از کلیک در خارج از ناحیهی آن، اجرا خواهد شد.
تعیین نوعهای پارامترهای هوک سفارشی
تا اینجا متوجه شدیم که handler، چیزی بجز یک تابع که void را بازگشت میدهد (void <= ())، نیست. همچنین نوع شیء ref را هم میتوان با نزدیک کردن اشارهگر ماوس، به متغیر ref در کامپوننت ReducerButtons، مشاهده کرد:
بر این اساس، تعاریف نوعهای پارامترهای هوک سفارشی useClickOutside به صورت زیر مشخص میشوند:
const useClickOutside = ( ref: React.RefObject<HTMLDivElement>, handler: () => void ) => {
const listener = (event: React.MouseEvent<HTMLElement>) => {
عنوان میکند که نوع event.target، از نوع Node، که مورد نظر متد contains است، نیست. برای رفع آن فقط کافی است تبدیل نوع زیر را انجام داد:
ref.current.contains(event.target as Node)
برای رفع این خطا، نوع پارامتر تابع handler را نیز بر اساس رویداد ارسالی به آن، مشخص میکنیم:
const useClickOutside = ( ref: React.RefObject<HTMLDivElement>, handler: (event: React.MouseEvent<HTMLElement>) => void ) => {
برای درک بهتر این خطا، اشارهگر ماوس را به محل تعریف این متد نزدیک میکنیم، تا بتوان امضای آنرا مشاهده کرد. در حالت mousedown، پارامتر دوم این متد، از نوع MouseEvent است:
(method) Document.addEventListener<"mousedown">(type: "mousedown", listener: (this: Document, ev: MouseEvent) => any, options?: boolean | AddEventListenerOptions | undefined): void (+1 overload)
(method) Document.addEventListener<"touchstart">(type: "touchstart", listener: (this: Document, ev: TouchEvent) => any, options?: boolean | AddEventListenerOptions | undefined): void (+1 overload)
const useClickOutside = ( ref: React.RefObject<HTMLDivElement>, handler: (event: MouseEvent | TouchEvent) => void ) => { useEffect(() => { const listener = (event: MouseEvent | TouchEvent) => {
یک نکتهی تکمیلی: در اینجا با تعریف <ref: React.RefObject<HTMLDivElement، دیگر ref ارسالی، هیچ المان دیگری را بجز div نمیتواند بپذیرد. برای عمومیتر کردن آن، میتوان بر روی آن کلیک راست کرد و گزینهی Go to definition را انتخاب نمود:
بنابراین حالت عمومیتر آن، استفاده از HTMLElement ای است که HTMLDivElement از آن ارث بری کردهاست:
const useClickOutside = ( ref: React.RefObject<HTMLElement>, handler: (event: MouseEvent | TouchEvent) => void ) => {
با این تغییرات، کدهای نهایی این قسمت، به صورت زیر در خواهند آمد:
import { useEffect } from "react"; const useClickOutside = ( ref: React.RefObject<HTMLElement>, handler: (event: MouseEvent | TouchEvent) => void ) => { useEffect(() => { const listener = (event: MouseEvent | TouchEvent) => { if (!ref.current || ref.current.contains(event.target as Node)) { return; } handler(event); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [handler, ref]); }; export { useClickOutside };
هنگامیکه درحال طراحی کلاسهایی هستیم که وابستگیهایی دارند، ممکن است با شرایطی مواجه شویم که به این وابستگیها نیاز نباشد و یا به رفتار عادی بعضی از وابستگیها نیاز نداشته باشیم. شاید راهی که در این مواقع به ذهن برسد این باشد که بجای شیء واقعی وابستگی موردنظر، از یک شیء Null Reference استفاده کنیم. ولی استفاده از این روش کدهایمان را پیچیده خواهد کرد؛ چون هر جای کد که نیازمند استفادهی از اعضای شیء وابستگی موردنظرمان باشیم، مثلا متدی را فراخوانی کنیم یا از یک پراپرتی آن استفاده کنیم، باید ابتدا از نال بودن یا نبودن آن اطمینان حاصل کنیم و سپس از آن استفاده نماییم؛ چون در غیر این صورت با خطای Null Pointer مواجه میشویم.
الگوی طراحی Null Object این مشکل را حل میکند که جای پاس دادن شیء Null Reference بجای شیء ای که واقعا به آن وابستگی وجود دارد و باید هر بار قبل استفادهی از آن بررسی کنیم که آیا آن شیء ای که داریم با آن کار میکنیم نال است یا خیر، کلاسی خاصی را بسازیم که یک وابستگی غیر کاربردی است. به این معنا که قرار نیست هیچ کاری را انجام دهد و عملا یک non-functional Dependency است. این کلاس یا یک اینترفیس خاصی را پیاده سازی میکند و یا اینکه از یک کلاس انتزاعی ارث بری خواهد کرد؛ ولی هیچ عملکرد خاصی را نخواهد داشت. به این معنا که متدها و پراپرتیهای این کلاس کاری را انجام نداده و یک مقدار پیشفرض و یا یک مقدار خاصی را برگشت خواهند داد. این روش به ساده سازی کد کمک خواهد کرد، چون میتوان بدون انجام پیش شرطهایی مانند بررسی نال بودن یا نبودن یک شیء وابسته، از آن استفاده کرد.
این الگوی طراحی معمولا همراه با دیگر الگوهای طراحی مورد استفاده قرار میگیرد. بهینهتر است که خود کلاس Null Object به صورت Singleton پیاده سازی شود. مزیت این کار در این است که چون شیء ساخته شده از این کلاس، نه کار خاصی را انجام میدهد و نه حالت خاصی را نگه میدارد، پس ساختن شیءای از آن عملا ضرورتی نداشته و هیچگونه ارزشی ندارد و فقط سرباری را بر روی نرم افزار قرار میدهد. پس سزاوار است فقط به یک شیء از این کلاس اکتفا کرد و هر بار همان شیء را برگشت داد. الگوی دیگری که غالبا از الگوی Null Object در آن استفاده میشود، الگوی Strategy است. زمانیکه یکی از استراتژیها این باشد که کار خاصی را انجام نداد و یا استراتژی مورد نظر عملکردی نداشته باشد، از الگوی Null Object استفاده میکنیم. الگوی دیگری که از الگوی Null Object زیاد استفاده میکند، الگوی Factory است. برای مثال هنگامیکه بخواهیم بر طبق شرایط برنامه یک شیء Null Reference را بسازیم و برگردانیم، از الگوی Null Object استفاده خواهیم کرد.
فرض کنید میخواهیم ماژولی را توسعه دهیم که وظیفهی آن گزارش دادن وضعیت وقوع رخدادها است و میخواهیم پیامهای وضعیت، به روشهای مختلفی مانند ارسال ایمیل و یا ثبت لاگ در سرورهای راه دور که برای لاگ گیری تعبیه شدهاند، انجام گیرد و در بعضی از مواقع هم میخواهیم برای برخی از رخدادها نیاز به گزارش نباشد. در این مواقع برای استراتژی سوم از الگوی طراحی Null Object استفاده میکنند.
پیاده سازی الگوی طراحی Null Object
کلاس دیاگرام زیر چگونگی پیاده سازی این الگو را نشان میدهد. در ادامه قصد داریم بخشهای مختلف این دیاگرام را توضیح دهیم.
Client : این کلاس دارای یک وابستگی به یک کلاس دیگر است که در بعضی مواقع نیازی به این وابستگی پیدا نمیکند و در صورتیکه به کارکرد اصلی وابستگی نیاز پیدا نکند، متدهای داخل کلاس Null Object را اجرا میکند.
DependencyBase : این قسمت کلاس پایهای است که به صورت Abstract بوده و شامل همه وابستگیهایی است که ممکن است Client به آن وابسته باشد. همچنین این بخش، کلاس پایهی کلاس Null Object هم است. شایان ذکر است که بجای استفاده از کلاس Abstract میتوان از یک Interface هم استفاده کرد؛ چون این کلاس هیچ عملکرد مشترکی را برای زیر کلاسهایش پیاده سازی نمیکند.
Dependency : این کلاس یک عملکرد واقعی از یک وابستگی است که Client به آن وابسته است.
NullObject : این همان کلاس Null Object است که به عنوان یک وابستگی توسط Client مورد استفاده قرار میگیرد. این کلاس هیچ عملکرد مشخصی را ندارد ولی باید تمام اعضای کلاس پایه، یعنی DependencyBase را پیاده سازی کند.
مثال زیر کدهای اصلی پیاده سازی الگوی طراحی Null Object را نشان خواهد داد که با زبان سی شارپ نوشته شدهاست. کلاس Client، وابستگیهای خود را از طریق سازنده دریافت خواهد کرد که به آن Constructor injection گفته میشود. همانطور که میبینید در کلاس NullObject، تنها متد Operation بازنویسی شده است و داخل آن هیچ عملکرد خاصی پیاده سازی نشده است؛ زیر تنها به وجود آن نیاز است و نه عملکرد داخلی آن.
public class Client { DependencyBase _dependency; public void Client(DependencyBase dependency) { _dependency = dependency; } public void DoSomething() { _dependency.Operation(); } } public abstract class DependencyBase { public abstract void Operation(); } public class Dependency : DependencyBase { public override void Operation() { Console.WriteLine("Dependency.Operation() executed"); } } public class NullObject : DependencyBase { public override void Operation() { } }
یک نمونه واقعی از الگوی طراحی Null Object
در این بخش قصد داریم مثالی از الگوی استراتژی را ارائه دهیم که در یکی از استراتژیهایش از کلاس Null Object استفاده خواهد کرد. در این مثال کلاسی وجود دارد به نام StatusMonitor که پس از انجام کارهایی، وضعیت انجام آن را اعلام میکند. ۳ نوع استراتژی برای اعلام وضعیت انجام کارها متصور است که بسته به موقعیتهای مختلف، یکی از آنها انتخاب خواهد شد. استراتژیهای اعلام وضعیت شامل ارسال ایمیل، ارسال وضعیت به یک وب سرویس و یا اصلا اعلام نکردن وضعیت هستند. زمانیکه قصد داریم هیچگونه وضعیتی اعلام نشود، از نمونهای از کلاس Null Object استفاده خواهد شد که در این مثال کلاس NullStatusReporter این وابستگی را تامین میکند. همه کلاسهای استراتژی که بیان شد تنها شامل یک متد هستند که از آن برای گزارش پیام وضعیت استفاده خواهیم کرد.
کلاسهای EmailStatusReporter و WebServiceStstusReporter در صورتیکه بتوانند به درستی پیامها را گزارش دهند، مقدار true را برگشت خواهند داد و در غیر اینصورت مقدار false برگشت داده میشود. اما کلاس Null Object هیچ کاری را انجام نمیدهد و چیزی را گزارش نمیدهد و تنها مقدار true را برگشت خواهد داد. اینکه این کلاس چه مقداری را برگشت دهد، قراردادی است که بین Client و Dependency انجام میگیرد. به این نکته هم توجه بفرمایید که کلاس NullStatusReporter به صورت Singleton پیاده سازی شده است.
public class StatusMonitor { StatusReporterBase _reporter; public StatusMonitor(StatusReporterBase reporter) { _reporter = reporter; } public void CheckStatus() { // Do something to check status if (!_reporter.Report("Everything's OK")) { Console.WriteLine("Failed to report status."); } } } public abstract class StatusReporterBase { public abstract bool Report(string message); } public class EmailStatusReporter : StatusReporterBase { public override bool Report(string message) { try { Console.WriteLine("Emailed '{0}'.", message); return true; } catch { return true; throw; } } } public class WebServiceStatusReporter : StatusReporterBase { public override bool Report(string message) { try { Console.WriteLine("Sent '{0}' to web service.", message); return true; } catch { return true; throw; } } } public class NullStatusReporter : StatusReporterBase { private static NullStatusReporter _instance; private static object _lock = new object(); private NullStatusReporter() { } public static NullStatusReporter GetReporter() { lock (_lock) { if (_instance == null) _instance = new NullStatusReporter(); } return _instance; } public override bool Report(string message) { return true; } }
تست کلاس Null Object
برای تست کلاس StatusMonitor باید یکی از انواع استرتژیها را برایش تعیین و آن را به سازنده کلاس تزریق کرد و با آن استراتژی، کلاس را تست نمود. در کد زیر از استراتژی NullObject استفاده شدهاست. پس یک نمونهی آن ساخته شده و از طریق سازنده به کلاس StatusMonitor فرستاده میشود. سپس متد CheckStatus فراخوانی میگردد. اما این متد کاری را انجام نمیدهد و تنها مقدار true برگشت داده میشود. بررسی روشهای دیگر را به خودتان واگذار میکنم.
StatusReporterBase reporter = NullStatusReporter.GetReporter(); StatusMonitor monitor = new StatusMonitor(reporter); monitor.CheckStatus();
تفاوت انواع var و dynamic
dynamic | var |
نیاز به مقدار دهی اولیه نداردdynamic foo; | نیاز به مقدار دهی اولیه داردvar foo = "it is a test string"; |
عدم نمایش intellisense | نمایش intellisense |
OpenCVSharp #11
الگوریتم k-Means clustering را میتوان به کمک یک مثال بهتر بررسی کرد. فرض کنید شرکت منسوجاتی قرار است پیراهنهای جدیدی را به بازار ارائه کند. بدیهی است برای فروش بیشتر، بهتر است پیراهنهایی را با اندازههای متفاوتی تولید کرد تا برای عموم مردم مفید باشد. اما ... برای این شرکت مقرون به صرفه نیست تا برای تمام اندازههای ممکن، پیراهن تولید کند. بنابراین اندازههای اشخاص را در سه گروه کوچک، متوسط و بزرگ تعریف میکند. این گروه بندی را میتوان توسط الگوریتم k-means clustering نیز انجام داد و به کمک آن به سه اندازهی بسیار مناسب رسید تا برای عموم اشخاص مناسب باشد. حتی اگر این سه گروه ناکافی باشند، این الگوریتم میتواند تعداد خوشه بندیهای متغیری را دریافت کند تا بهینهترین پاسخ حاصل شود. [برای مطالعه بیشتر]
ارتباط الگوریتم k-means clustering با مباحث پردازش تصویر، در پیش پردازشهای لازمی است که جهت سرفصلهایی مانند تشخیص اشیاء، آنالیز صحنه، ردیابی و امثال آن ضروری هستند. از الگوریتم خوشه بندی k-means عموما جهت مفهومی به نام Color Quantization یا کاهش تعداد رنگهای تصویر استفاده میشود. یکی از مهمترین مزایای این کار، کاهش فشار حافظه و همچنین بالا رفتن سرعت پردازشهای بعدی بر روی تصویر است. همچنین گاهی از اوقات برای چاپ پوسترها نیاز است تعداد رنگهای تصویر را کاهش داد که در اینجا نیز میتوان از این الگوریتم استفاده کرد.
پیاده سازی الگوریتم خوشه بندی K-means
در ادامه کدهای بکارگیری متد kmeans کتابخانهی OpenCV را به کمک OpenCVSharp مشاهده میکنید:
var src = new Mat(@"..\..\Images\fruits.jpg", LoadMode.AnyDepth | LoadMode.AnyColor); Cv2.ImShow("Source", src); Cv2.WaitKey(1); // do events Cv2.Blur(src, src, new Size(15, 15)); Cv2.ImShow("Blurred Image", src); Cv2.WaitKey(1); // do events // Converts the MxNx3 image into a Kx3 matrix where K=MxN and // each row is now a vector in the 3-D space of RGB. // change to a Mx3 column vector (M is number of pixels in image) var columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols); // convert to floating point, it is a requirement of the k-means method of OpenCV. var samples = new Mat(); columnVector.ConvertTo(samples, MatType.CV_32FC3); for (var clustersCount = 2; clustersCount <= 8; clustersCount += 2) { var bestLabels = new Mat(); var centers = new Mat(); Cv2.Kmeans( data: samples, k: clustersCount, bestLabels: bestLabels, criteria: new TermCriteria(type: CriteriaType.Epsilon | CriteriaType.Iteration, maxCount: 10, epsilon: 1.0), attempts: 3, flags: KMeansFlag.PpCenters, centers: centers); var clusteredImage = new Mat(src.Rows, src.Cols, src.Type()); for (var size = 0; size < src.Cols * src.Rows; size++) { var clusterIndex = bestLabels.At<int>(0, size); var newPixel = new Vec3b { Item0 = (byte)(centers.At<float>(clusterIndex, 0)), // B Item1 = (byte)(centers.At<float>(clusterIndex, 1)), // G Item2 = (byte)(centers.At<float>(clusterIndex, 2)) // R }; clusteredImage.Set(size / src.Cols, size % src.Cols, newPixel); } Cv2.ImShow(string.Format("Clustered Image [k:{0}]", clustersCount), clusteredImage); Cv2.WaitKey(1); // do events } Cv2.WaitKey(); Cv2.DestroyAllWindows();
توضیحات
- ابتدا تصویر اصلی برنامه بارگذاری میشود و در یک پنجره نمایش داده خواهد شد. در اینجا متد Cv2.WaitKey را با پارامتر یک، مشاهده میکنید. این فراخوانی ویژه، شبیه به متد do events در برنامههای WinForms است. اگر فراخوانی نشود، تمام تصاویر پنجرههای مختلف برنامه تا زمان پایان پردازشهای مختلف برنامه، نمایش داده نخواهند شد و تا آن زمان صرفا یک یا چند پنجرهی خاکستری رنگ را مشاهده خواهید کرد.
- در ادامه متد Blur بر روی این تصویر فراخوانی شدهاست تا مقداری تصویر را مات کند. هدف از بکارگیری این متد در این مثال، برجسته کردن خوشه بندی گروههای رنگی مختلف در تصویر اصلی است.
- سپس متد Reshape بر روی ماتریس تصویر اصلی بارگذاری شده فراخوانی میشود.
هدف از بکارگیری الگوریتم k-means، انتساب برچسبهایی به هر نقطهی RGB تصویر است. در اینجا هر نقطه به شکل یک بردار در فضای سه بعدی مشاهده میشود. سپس سعی خواهد شد تا این MxN بردار، به k قسمت تقسیم شوند.
متد Reshape تصویر اصلی MxNx3 را به یک ماتریس Kx3 تبدیل میکند که در آن K=MxN است و اکنون هر ردیف آن برداری است در فضای سه بعدی RGB.
- پس از آن توسط متد ConvertTo، نوع دادههای این ماتریس جدید به float تبدیل میشوند تا در متد kmeans قابل استفاده شوند.
- در ادامه یک حلقه را مشاهده میکنید که عملیات کاهش رنگهای تصویر و خوشه بندی آنها را 4 بار با مقادیر مختلف clustersCount انجام میدهد.
- در متد kmeans، پارامتر data یک ماتریس float است که هر نمونهی آن در یک ردیف قرار گرفتهاست. K بیانگر تعداد خوشهها، جهت تقسیم دادهها است.
در اینجا پارامترهای labels و centers خروجیهای متد هستند. برچسبها بیانگر اندیسهای هر خوشه به ازای هر نمونه هستند. Centers ماتریس مراکز هر خوشه است و دارای یک ردیف به ازای هر خوشه است.
پارامتر criteria آن مشخص میکند که الگوریتم چگونه باید خاتمه یابد که در آن حداکثر تعداد بررسیها و یا دقت مورد نظر مشخص میشوند.
پارامتر attempts مشخص میکند که این الگوریتم چندبار باید اجرا شود تا بهترین میزان فشردگی و کاهش رنگ حاصل شود.
- پس از پایان عملیات k-means نیاز است تا اطلاعات آن مجددا به شکل ماتریسی هم اندازهی تصویر اصلی برگردانده شود تا بتوان آنرا نمایش داد. در اینجا بهتر میتوان نحوهی عملکرد متد k-means را درک کرد. حلقهی تشکیل شده به اندازهی تمام نقاط طول و عرض تصویر اصلی است. به ازای هر نقطه، توسط الگوریتم k-means یک برچسب تشکیل شده (bestLabels) که مشخص میکند این نقطه متعلق به کدام خوشه و cluster رنگهای کاهش یافته است. سپس بر اساس این اندیس میتوان رنگ این نقطه را از خروجی centers یافته و در یک تصویر جدید نمایش داد.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.