[ Log On ]
ของเล่นใหม่ใน C# 5.0

ในการเปิดตัว Visual Studio 2012 หรือ Visual Studio รุ่นที่ 11 นั้น สิ่งที่ติดตามมาด้วยคือ .NET CLR 4.5, Visual Basic (VB) 11 และ C# 5 และเนื่องจากผมเขียน VB ไม่เป็น (แม้ว่าจะพออ่านออกบ้างก็ตาม) ผมจึงขอเน้นไปที่ C# 5.0 ก็แล้วกัน

สิ่งที่เป็นของเล่นใหม่อย่างชัดเจนใน C# 5.0 นั้น ถ้าดูจากตารางพัฒนาการด้านล่างนี้ มีอยู่ 2 อย่างครับ นั่นคือ Async และ Caller

Async Feature

ใน Async นั้นมี keyword 2 ตัวที่เพิ่มเข้ามา คือ async (modifier) และ await (operator) โดย method ที่มีการใส่ async กำกับไว้ จะเรียกว่า async method ในการเขียนโปรแกรมแนว asynchronous นั้น ช่วยทำให้การทำงานของโปรแกรมที่เราพัฒนาขึ้นมานั้นทำงานได้ราบลื่นขึ้น เช่น สมมติว่าเราเขียน Windows app ขึ้นมาตัวนึง แล้วต้องเรียกใช้ HttpWebRequest ถ้าเราไม่ได้เขียนแบบ async ไว้ เวลา HttpWebRequest ถูกเรียกใช้ app ของเราก็จะถูก block ให้รอจน HttpWebRequest ได้ response กลับมาเรียบร้อยแล้ว.. ซึ่งถ้าเราทำให้เป็น async method ซะ เราก็สามารถให้ app ของเราทำงานอื่น ๆ ไปด้วยได้ ในขณะที่รอ response จาก HttpWebRequest

private void btnTest_Click(object sender, EventArgs e)
{
    var request = WebRequest.Create(txtUrl.Text.Trim());
    var content = new MemoryStream();
    using (var response = request.GetResponse())
    {
        using (var responseStream = response.GetResponseStream())
        {
            responseStream.CopyTo(content);
        }
    }
    txtResult.Text = content.Length.ToString();
}

จากตัวอย่างข้างต้น ถ้าหากว่าเราพิมพ์ URL แล้วกดที่ปุ่ม Test เราจะไม่สามารถทำการแก้ไข URL ได้เลย จนกว่าจะได้ response กลับมาจนครบ (txtResult แสดงค่าในกล่องล่างสุด)

ก่อนหน้าที่จะมี async modifier มา เราจะใช้ BeginGetResponse method ในการส่ง async request แต่ขอบอกไว้ตรงนี้เลยว่า มันวุ่นวายมวาาาาาก กว่าจะเข้าใจ (ดูตัวอย่างการใช้งานได้ใน MSDN)

แต่ตอนนี้ เรามี async modifier แล้ว ชีวิตเราจะง่ายขึ้นไปเป็นกองครับ ดูจาก code ด้านล่างนี้แล้วไปเทียบกับ code ด้านบน และ ใน MSDN ดูเองนะครับ

private async void btnTest_Click(object sender, EventArgs e)
{
    var request = WebRequest.Create(txtUrl.Text.Trim());
    var context = new MemoryStream();
    Task<WebResponse> responseTask = request.GetResponseAsync();
    using (var response = await responseTask)
    {
        using (var responseStream = response.GetResponseStream())
        {
            Task copyTask = responseStream.CopyToAsync(content);
            
            // await operator to suspends the execution of the method until the task is completed.
            // in the meantime, the control is returned the UI thread.

            await copyTask;
        }
    }
    txtResult.Text = content.Length.ToString();

}

await operator จะถูกกำกับอยู่หน้า task ที่จะถูก return กลับออกมา โดยมันจะไปจำศีล method นี้จนกว่า task จะทำงานเสร็จสมบูรณ์แล้ว แต่ในระหว่างที่มันจำศีลอยู่ thread ของส่วนที่เป็น UI จะถูกปล่อยกลับคืนมาในระบบ ทำให้ control ยังสามารถทำงานได้ (ไม่มีการ block เหมือน code ชุดข้างบน)

ทั้งนี้ทั้งนั้น ใช่ว่าทุกอย่างจะต้องปรับไปใช้ async ซะทั้งหมด เพราะการโปรแกรมแบบ async นั้นจะใช้ thread ใช้ memory .. ถ้ามี async มากเกินไป โอกาสที่ memory เต็มก็อาจจะเกิดขึ้นได้ แต่อย่างน้อย ใน Visual Studio 2012 นี้ ถ้าหากว่ามีการเขียน async method แล้วทำงานไม่ถูกต้อง ตัว IDE ก็ฉลาดพอที่จะเตือนเราได้ครับ ว่าเขียนไม่ถูกวิธี ไว้ถ้ามีโอกาสจะมาเล่าเรื่องนี้อีกทีภายหลัง

Caller Information

Caller Information จะช่วยให้เราเหล่า programmer ทำงานได้ง่ายขึ้นในเรื่องของการ tracing, debugging และการสร้างเครื่องมือสำหรับการวิเคราะห์/ตรวจสอบโปรแกรมของเรา (diagnose tools) เราจะได้คอยตามดูได้ว่า code ของเราไม่ทำงานซับซ้อนกัน หรือมีการเรียกใช้งานมาจากที่ไหน ส่วนไหน เรียกไปทำงานอะไร ฯลฯ เช่น logging และ tracing

หาข้อมูลเพิ่มเติมเกี่ยวกับ Caller method ได้จาก link เหล่านี้ครับ

  • CallerFilePathAttribute Full path ของ source code ที่เรียกมา โดยจะเป็น file path ณ ตอน compile
  • CallerLineNumberAttribute เลขที่บรรทัดใน method ใน source code ที่เรียกมา
  • CallerMemberNameAttribute ชื่อ method หรือ property ที่เรียกมา

ข้างล่างนี้เป็นตัวอย่างการเขียน log แบบ common practice ก่อนหน้าที่จะมี Caller Information มาให้ใช้

using System;
using System.Collection.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplicationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            InsertLog("Main");
            MethodB();
            Console.ReadLine();
        }

        static void MethodA()
        {
            InsertLog("MethodA");
            MethodB();
        }

        static void MethodB() {}

        static void InsertLog(string methodName)
        {
            Console.WriteLine("{0} called method B at {1}", methodName, DateTime.UtcNow);
        }
    }
}

ทั้งใน Main method และ MethodA method ต่างก็เรียกใช้ InsertLog method เพื่อ log การใช้งาน ทีนี้เราลองมาเปลี่ยนมาใช้แบบ Caller Information ดู

using System;
using System.Collection.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplicationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //InsertLog("Main");
            MethodB();
            Console.ReadLine();
        }

        static void MethodA()
        {
            //InsertLog("MethodA");
            MethodB();
        }

        static void MethodB(
            [CallerMemberName]string memberName = "",
            [CallerFilePath]string sourceFilePath = "",
            [CallerLineNumber]int sourceLineNumber = 0
)
        {
            InsertLog(memberName);
        }

        static void InsertLog(string methodName)
        {
            Console.WriteLine("{0} called method B at {1}", methodName, DateTime.UtcNow);
        }
    }
}