Lazy<T> ใน C#

Chinakit Pinclay
2 min readMar 9, 2024

--

ในบทความนี้เราจะมาทำความรู้จักกับ 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

--

--

No responses yet