เขียน C# ง่าย ๆ สไตล์ Ruby
เดิมที ผมเขียน Blog นึง ไว้จัดการความรู้ของผม ที่ได้จากการเล่นเว็บ www.projecteuler.net Blog ของผมชื่อ chaowchaow.blogspot.com ซึ่งเขียนเป็นภาษาอังกฤษ และใช้สำนวนมั่ว ๆ ของผมเอง แต่พอดีได้ไปอ่าน Blog คุณศล เลยเกิดแรงบันดาลใจ จะเขียนเป็นภาษาไทยบ้าง เพื่อที่จะได้แบ่งปันความรู้กัน โดยจะพยายามจะย้ายมาที่ละบทความนะครับ
รู้จัก Ruby กันมั๊ยครับ ? Ruby เป็นภาษาสำหรับเขียน Program ที่เน้นความง่าย สั้น แต่ทำอะไรได้เยอะ ภาษา Ruby สามารถเขียนได้กระชับมาก จนมีเว็บ สำหรับแข่งเขียน Code ให้สั้นที่สุดกัน
ผิดกับ C# ซึ่งเทียบกับ Ruby แล้ว C# ถือเป็นภาษาสุภาพ ต้องพูดตามไวยกรณ์ ความหมายชัดเจน อย่างแรกคุณต้องประกาศชนิดของตัวแปรก่อน ถ้าเป็น Array ก็ต้องกำหนด Size ให้แน่นอน จะกำหนดค่าให้ตัวแปร ก็ต้อง Check Type กันวุ่นวาย อย่างนี้จะเขียน C# แบบสนุก ๆ ไม่ได้นะสิ ต้องเขียนแบบเป็นเรื่องเป็นราวใช่มั๊ย ?
ต้องไม่สิครับ เราจะมาทำรัฐประหารกัน เราต้องทำให้ C# เขียนง่ายแบบไม่เคยรู้สึกมาก่อน C# ออก Version ใหม่แล้วนะ Version 3.0 มี Features ใหม่มากมาย ผมขออนุญาตไม่อธิบายรายอันว่า Features ต่าง ๆ ทำอะไรได้บ้างนะครับ
คือไอ้ C# ตัวใหม่เนี่ยมันทำอะไรที่เรียกว่า Extension Methods ได้ ปกติเวลาเราต้องการเพิ่มความสามารถให้ Class ๆ นึง เราก็ต้องสร้าง Class ลูกแยกออกมาแล้วเพิ่ม Method ลงไป แต่ถ้าทำอย่างนี้ Class ก็มีให้จำเพิ่มขึ้น แล้วก็ขี้เกียจเขียน Class ใหม่ด้วยใช่ไหมครับ
Extension Methods นี้มันมาแก้ปัญหานี้โดยเฉพาะ มันเป็นการสร้าง Static Method ขึ้นมา แล้วก็กำหนดคำว่า this หน้า Parameter แรก แค่นี้เราก็หลอก C# (กับหลอกตัวเอง) ได้ว่านี่คือ Method ใหม่ของ Type แรกใน Parameter นะ
เช่น เราสร้าง Static Method สำหรับแปลง String เป็น Int ขึ้นมา
public static int ToInt(this string source) { return int.Parse(source); }
เท่านี้เราก็สามารถสร้าง Method ใหม่ ชื่อ ToInt ให้กับ String ได้ วิธีใช้ก็
int i = "123".ToInt();
เท่านี้ String ที่มีค่า "123" ซึ่งไม่เคยมี Method ชื่อ ToInt มาก่อน ก็สามารถเรียก ToInt ได้
ทีนี้เรามาเปลี่ยน C# ให้เป็นคนใหม่กัน เริ่มจากคำว่า foreach foreach มีไว้แยก item แต่ละตัวใน Class ประเภท Enumerable Enumerable คือ สามารถแตกย่อยออกมาได้ เช่น List, Array, Dictionary วิธีเขียน foreach คือ
foreach (Object item in list)
ทีนี้มาเทียบกับ Ruby
list.each{|item| ...}
Ruby สั้นและเข้าใจง่ายกว่าใช่ไหมครับ มีประธาน คือ list กริยาคือ each ส่วนในวงเล็บ { } คือ บอกว่าทำอะไร แปลเป็นภาษาไทยคือ "List นี้ แตกออกมาแต่ละ item ทำ ..."
ทีนี้เรามาสร้าง Extension Methods กัน ก่อนอื่นคุณต้องสร้าง Static Class ขึ้นมา หนึ่งอัน ชื่ออะไรก็ได้ แล้ว Copy Code ข้างล่าง ไปใส่ใน Static Class นั้น
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) { foreach (T item in source) action(item); } public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action) { int i = 0;
foreach (T item in source) action(item, i++); }
เวลาคุณจะต้องการให้ C# รู้จัก Extension Methods ที่สร้างมาใหม่ คุณต้องกำหนด "using" ตามด้วย Namespace ของ Static Class ไว้ด้านบนสุด ของ Class ที่เราต้องการใช้ Extension Method ด้วยนะครับ ซึ่งถ้าทำเสร็จแล้ว เราจะสามารถเขียนแบบนี้ได้
list.ForEach(item => ...); list.ForEach((item,index)=> ...);
งงไหมครับ ไปดูตัวอย่างกัน
string[] persons = new string[] {"Jack", "Lisa", "Ryan"}; persons.ForEach(person => Console.WriteLine("Hello {0}!", person)); //Hello Jack! //Hello Lisa! //Hello Ryan!
persons.ForEach((person, index) => Console.WriteLine("{0}. {1}", index + 1, person)); //1. Jack //2. Lisa //3. Ryan
ไอ้เครื่องหมาย => เค้าเรียกว่า Lambda Expression มันคือ Anonymous Method หรือ Method ย่อยนั่นเอง ด้านซ้ายของ => คือ Parameter ถ้ามี Parameter เดียว ไม่ต้องมีวงเล็บก็ได้ และหน้าตัวแปร ไม่ต้องประกาศชนิดของตัวแปรก็ได้ ด้านขวาของ => คือ ตัว Body ของ Method ย่อย
ในที่นี้ Code ด้านบน Code แรก มี Parameter เดียว คือ person person คือ item ที่แตกออกมาจาก persons และมีคำสั่งให้ Console เขียนคำว่า Hello แล้วตามด้วยชื่อ ผลลัพธ์คือ Console จะเขียนคำว่า Hello และชื่อ สำหรับทุก ๆ ชื่อใน persons
Code ที่ 2 มี 2 Parameters อันแรกคือ item อันที่ 2 คือ index index นี้จะเริ่มจาก 0 แล้วรันตัวเลขไปเรื่อย ๆ ทุก ๆ item ผลลัพธ์เลยทำให้เวลา Console เขียนออกมาแล้วรัน 1,2,3 ได้
พอเข้าใจไหมครับ ไม่เข้าใจตรงไหนถามได้ครับ ต่อมา คือ คำสั่ง for นะครับ เวลาเราจะรันตัวเลขใน C# เราเขียน
for (int i = 0; i <= 2; i++)
แต่ Ruby ง่ายกว่าครับ แค่ประกาศ Range 0-2
0..2
หรือมีอีกทางนึงเลย คือบอกว่าทำ 3 รอบ
3.times{|x| ..}
ไอ้ค่า x ด้านบนก็จะเป็น 0-2 ให้อัตโนมัติ อิจฉา Ruby มันรึยังครับ? ถ้าอิจฉาเอา Code ด้านล่าง ไปแปะที่ Static Class ที่คุณไว้รวม Extension Methods
public static IEnumerable<int> To(this int from, int to) { for (int x = from; x <= to; x++) yield return x; } public static IEnumerable<int> DownTo(this int from, int to) { for (int x = from; x >= to; x--) yield return x; } public static IEnumerable<int> StepTo(this int from, int to, int step) { if (step > 0) { for (int x = from; x <= to; x += step) yield return x; } else if (step < 0) { for (int x = from; x >= to; x += step) yield return x; } else { throw new ArgumentException("Step cannot be zero.", "step"); } } public static void Times(this int num, Action<int> action) { for (int x = 0; x < num; x++) action(x); }
มีหลายคำสั่งนะครับ เวลาเอาไปใช้จะได้ตามนี้
string[] persons = new string[] { "Jack", "Lisa", "Ryan" }; 0.To(2).ForEach(i => Console.WriteLine(persons[i])); //Jack //Lisa //Ryan
2.DownTo(0).ForEach(i => Console.WriteLine(persons[i])); //Ryan //Lisa //Jack
0.StepTo(2, 2).ForEach(i => Console.WriteLine(persons[i])); //Ryan //Jack
3.Times(i => Console.WriteLine(persons[i])); //Jack //Lisa //Ryan
คำสั่ง 0.To(2) หมายถึง การสร้าง Range เริ่มจาก 0, 1, 2 แต่ถ้าเป็น 2.To(0) Range นี้จะว่างเปล่าเพราะคำสั่งนี้รันจากน้อยไปมากเท่านั้น
คำสั่ง 2.DownTo(0) คือการทำ Range จากมากไปน้อย ในที่นี้จะเริ่มจาก 2, 1, 0
คำสั่ง 0.StepTo(2, 2) คือ การสร้าง Range แบบกระโดด Parameter แรกคือ ตัวเลขสิ้นสุด Parameter ที่ 2 คือ ระยะของแต่ละตัวเลข เช่น ถ้าเป็น 1.StepTo(5, 2) คือ การรันเริ่มจาก 1, 3, 5 และกระโดดถอยหลังได้ โดยใส่ระยะเป็นติดลบ เช่น 5.StepTo(1, -2) คือการรันจาก 5, 3, 1 ระยะใส่เป็น 0 ไม่ได้นะครับ เช่น 1.StepTo(2, 0) จะ Error
คำสั่งสุดท้าย 3.Times อันนี้จะเป็นการทำซ้ำ 3 ครั้ง โดยต้องโยน Lambda Expression เข้าไป Parameter แรกของ Lambda คือ index ของรอบการรัน เริ่มจาก 0
ที่เขียนมาทั้งหมด ก็เพื่อทำโจทย์ข้อแรกของ projecteuler.net เองนะครับ Add all the natural numbers below 1000 that are multiples of 3 or 5. แปลเป็นไทยคือ บวกจำนวนนับ 1 - 999 ทุกจำนวนที่เป็นผลคูณของเลข 3 หรือ 5 เขียนได้ตามนี้ครับ
Console.WriteLine(1.To(999).Where(x => (x % 3 == 0) || (x % 5 == 0)).Sum()); //233168
เคยคิดไหมครับว่า C# จะแก้ปัญหาด้านบนด้วย Code บรรทัดเดียว! แค่ลองเปลี่ยนสไตล์ C# ของเราวัยรุ่นขึ้นเยอะเลยจริงไหมครับ
* อ่านจบแล้ว กรุณา Comment ด้วยครับ เพื่อเป็น Feedback และเป็นกำลังใจ * บทความนี้คัดลอกได้ แต่ต้องมี Link หรือ Url กลับมาที่หน้านี้ และเมื่อคัดลอกแล้วกรุณา Comment แจ้งด้วยครับ
Create Date : 21 มิถุนายน 2551 |
Last Update : 9 สิงหาคม 2551 14:46:40 น. |
|
19 comments
|
Counter : 5438 Pageviews. |
|
|