C# 顯式轉換和隱含轉換 使用時機和方法介紹

在 C# 中我們常會將實體資料模型命名為 Entity,用以直接和資料庫進行存取,而在使用者需要資料時並不會直接將 Entity 提供給使用者,通常會將 Entity 轉換為僅傳輸用的 DTO (資料傳輸物件) 或是用於頁面顯示並驗證的 ViewModel,用以隱藏屬性或避免暴露弱點。

提供幾個簡單的範例查看各個的區別:

UserEntity: 直接和資料庫進行存取
    
public class UserEntity
{
    [Key] [Column("id")] public string Id { get; set; } = string.Empty;

    [Column("username")]
    [StringLength(256)]
    public string UserName { get; set; }

    [Column("email")] [StringLength(256)] public string Email { get; set; }
}

    

DTO: 僅作為資料傳輸使用
    
public class UserDto
{
    public string UserName { get; set; }
    public string Email { get; set; }
}
    

ViewModel: 一個頁面所會用到的資料組合而成,並可加上驗證功能
    
public class UserViewModel
{
    [StringLength(20, MinimumLength = 5, ErrorMessage = "使用者名稱長度必須介於5~20個字元")]
    public string UserName { get; set; }

    [EmailAddress(ErrorMessage = "Email格式錯誤")]
    public string Email { get; set; }
}
    

稍稍離題了,本文的主要目的是介紹「轉換」。什麼時候會需要使用到轉換呢?例如上面的例子從資料庫中取出 UserEntity,轉換為 UserDto 提供給 API 存取,或是轉換為 UserViewModel 顯示在頁面上。這時候就會需要使用到「轉換」了

例如:
    
        // 從資料庫中查詢
        UserEntity? entity = _dbContext.Users.FirstOrDefault(x => x.Id == "1");
		
        // 手動將 UserEntity 轉換為 UserDto
        UserDto dto = new UserDto
        {
            UserName = entity.UserName,
            Email = entity.Email
        };
    

但每次都需要這樣手動轉換很無聊且浪費時間,這時候就可以先定義好該如何轉換
    
public class UserDto
{
    public string UserName { get; set; }
    public string Email { get; set; }

	// 定義隱含轉換
    public static implicit operator UserDto(UserEntity entity)
    {
        return new UserDto
        {
            UserName = entity.UserName,
            Email = entity.Email
        };
    }
}
    

這樣使用時就可以很簡單的使用隱含(implicit)轉換省去手動對應了
    
// 從資料庫中查詢
UserEntity? entity = _dbContext.Users.FirstOrDefault(x => x.Id == "1");
// 隱含轉換
UserDto dto = entity;
    

不過這樣很容易的就轉換完畢,有時候可能沒仔細注意到造成問題,所以在面對「資料不會完全轉換」的轉換時通常會使用顯式轉換 (explicit) ,需要開發人員明確指定型別才能轉換。
    
public class UserDto
{
    public string UserName { get; set; }
    public string Email { get; set; }

	// 定義顯式轉換
    public static explicit operator UserDto(UserEntity entity)
    {
        return new UserDto
        {
            UserName = entity.UserName,
            Email = entity.Email
        };
    }
}
    

轉換時變成需要明確指定型別才能完成轉換
    
// 從資料庫中查詢
UserEntity? entity = _dbContext.Users.FirstOrDefault(x => x.Id == "1");
// 顯式轉換
UserDto dto = (UserDto)entity;
    

注意事項:
  • 需要使用靜態類別 static
  • 同種轉換顯式和隱含轉換只能擇一 (UserEntity 到 UserDto 只能顯式或是隱含)
  • 若轉換的類別有關聯時,建議提取出父類別或是介面,避免無謂的轉換


當然,如果覺得這樣要先寫好對應很麻煩,也可以使用第三方套件如 AutoMapper 等方式取代,只是建議先評估需要轉換的數量、了解該套件是如何轉換、面對名稱並非直接對應時的處理邏輯,避免有時太過依賴第三方套件造成除錯上的額外時間花費和留下安全隱患。

參考資料:
Microsoft.Learn - User-defined conversion operators (C# reference)
Microsoft.Learn - Creating Model Classes with the Entity Framework (C#)
Microsoft.Learn - Create Data Transfer Objects (DTOs)

留言