Lazy<T> ใน C#
ในบทความนี้เราจะมาทำความรู้จักกับ Lazy ใน C# โดยมันจะเกี่ยวข้องกับ Dependency Injection (DI) หรือ Inversion of Control (IoC)
มีความสำคัญยังไง
เมื่อเราเขียน C# หนึ่งในสิ่งที่เป็น Best Practics คือการทำ Dependency Injection ที่เราต้อง Inject Service ต่าง ๆ เข้าไป แต่เราอาจจะเคยเจอปัญหาที่ฟังก์ชันในคลาสไม่ได้ใช้ Service ที่ Inject เข้ามา แล้วเราจะใช้งาน Dependency เหล่านั้นอย่างไรโดยไม่ต้องทำการ inject ผ่าน constructor
เราจะสาธิตด้วย .NET 8
เราจะสร้าง Interface สำหรับ Service โดยจะเราสร้างมาทั้งหมด 3 Service (ในที่นี้จะรวมเป็นไฟล์เดียวเพื่อให้ดูเรียบร้อย แต่จริง ๆ มันจะต้องแยกเป็นหนึ่งไฟล์ต่อหนึ่ง interface)
//IServiceA.cs
public interface IServiceA
{
public void MethodA();
}
//IServiceB.cs
public interface IServiceB
{
public void MethodB();
}
//IServiceC.cs
public interface IServiceC
{
public void MethodC();
public void MethodC_A();
public void MethodC_B();
}
ทำการ implement แต่ละ interface (แล้วเหมือนเดิมขอรวมในไฟล์เดียวเพื่อความเรียบร้อย)
//ServiceA.cs
public class ServiceA : IServiceA
{
public void MethodA()
{
Console.WriteLine("MethoA");
}
}
//ServiceB.cs
public class ServiceB : IServiceB
{
public void MethodB()
{
Console.WriteLine("MethoB");
}
}
//ServiceC.cs
public class ServiceC : IServiceC
{
private readonly Lazy<IServiceA> _serviceA;
private readonly Lazy<IServiceB> _serviceB;
public ServiceC(Lazy<IServiceA> serviceA, Lazy<IServiceB> serviceB)
{
_serviceA = serviceA;
_serviceB = serviceB;
}
public void MethodC()
{
Console.WriteLine("MethoC");
}
public void MethodC_A()
{
Console.WriteLine("MethoC_A");
_serviceA.Value.MethodA();
}
public void MethodC_B()
{
Console.WriteLine("MethoC_B");
_serviceB.Value.MethodB();
}
}
โดยจะเห็นว่าเราได้ใช้ Lazy ในการ inject ServiceA และ ServiceฺB ไปใน ServiceC
ต่อไปเราก็จะสร้าง static class ในการ registry dependency โดยในการใช้ Lazy จะต้องมีการเพิ่มโค้ด
//Dependency.cs
public static class Dependencies
{
public static void AddAppDependencies(this IServiceCollection services)
{
services.AddTransient<IServiceA, ServiceA>();
services.AddTransient<IServiceB, ServiceB>();
services.AddTransient<IServiceC, ServiceC>();
// การ registor Lazy โดยจะมีการเพิ่มโค้ดขึ้นมาเล็กน้อย
services.AddTransient<Lazy<IServiceA>>(provider => new Lazy<IServiceA>(() => provider.GetRequiredService<IServiceA>()));
services.AddTransient<Lazy<IServiceB>>(provider => new Lazy<IServiceB>(() => provider.GetRequiredService<IServiceB>()));
}
}
เพิ่มฟังก์ชันเข้าไปใน Program.cs
//Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAppDependencies(); // <- เพิ่มบรรทัดนี้
var app = builder.Build();
และสุดท้ายเราจะสร้าง Controller เพื่อเป็น Endpoint ให้เราสามารถทดสอบ Dependency ได้
using LazyDi.Service;
using Microsoft.AspNetCore.Mvc;
namespace LazyDIApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class LazyController : ControllerBase
{
private readonly IServiceC _serviceC;
public LazyController(IServiceC serviceC)
{
_serviceC = serviceC;
}
[HttpGet("MethodC")]
public string Get_UseC()
{
_serviceC.MethodC();
return "Success";
}
[HttpGet("MethodC_A")]
public string Get_UseC_A()
{
_serviceC.MethodC_A();
return "Success";
}
[HttpGet("MethodC_B")]
public string Get_UseC_B()
{
_serviceC.MethodC_B();
return "Success";
}
[HttpGet("MethodC_AB")]
public string Get_UseC_AB()
{
_serviceC.MethodC_A();
_serviceC.MethodC_B();
return "Success";
}
}
เราจะได้ Endpoint ต่อไปนี้
โดยเมื่อเรา run ในโหมด debug แล้วเรียกใช้งาน /api/Lazy/MethodC_B เราจะเห็นได้ว่า _serviceA จะเป็น null ส่วน _serviceB ที่ ServiceC เรียกใช้ในฟังก์ชัน MethodC_B จะถูกสร้างขึ้น
สรุป
การใช้งาน Lazy<T> เป็นการที่ Dependency จะถูก Inject เมื่อมีการเรียกใช้ (execute) เท่านั้น อย่างในตัวอย่างจะเห็นว่า ServiceC จะถูกใช้งานเมื่อเกิด API call ซึ่ง Lazy<T> จะช่วยเรื่อง startup time ของ application ได้ดีกว่า AddSingleton อีกทั้งยังช่วยเรื่องการจัดการ memory