簡體   English   中英

有沒有辦法使用對象的形狀而不是繼承來對通用函數參數進行類型檢查?

[英]Is there a way to type check a generic function parameter using the shape of the object instead of inheritance?

我有一些想要創建的 EF 模型類。 每個類都有一些我想在插入新實體之前設置的公共屬性,例如:

public partial class BlogPost {
  public DateTime CreatedTime { get; set; }
  public string CreatorName { get; set; }
  public string PostTitle { get; set; }
  public string PostText { get; set; }
}

public partial class Comment {
  public DateTime CreatedTime { get; set; }
  public string CreatorName { get; set; }
  public string CommentText { get; set; }
}

...

當我創建這些類時,我像這樣實例化它們:

var blogPost = new BlogPost {
  CreatedTime = DateTime.UtcNow,
  CreatorName = creatorName,
  PostTitle = postTitle,
  PostText = postText,
};

var comment = new Comment {
  CreatedTime = DateTime.UtcNow,
  CreatorName = creatorName,
  ...
};

...

我想創建一個方法來自動設置一些公共屬性,這樣我就不需要為每個具有相同屬性的類手動輸入它們。 由於它們不擴展相同的類或實現相同的接口,我想知道如何實現這一點。 我的第一個想法是使用泛型方法; 但是,我不知道是否有一種方法可以指定泛型類型應該具有哪些屬性而不擴展相同的類(類似於 TypeScript 的“鴨子類型” )。 我想要的方法看起來像這樣:

public void SetInitialProperties<T>(T dbEntity, DateTime createdTime, string creatorName) where T : ??? {
  dbEntity.CreatedTime = createdTime;
  dbEntity.CreatorName = creatorName;
}

...

var blogPost = new BlogPost { PostTitle = postTitle, PostText = postText };
SetInitialProperties(blogPost, createdTime, creatorName);

最壞的情況是,如果我不能使用泛型,我總是可以使用dynamic 但是,如果可能,我想繼續進行類型檢查。

您不能在 C# 中這樣做,因為 C# 使用名義類型系統而不是結構類型系統
對於您的特定情況,您必須提出一個包含共同屬性的接口,並且將由兩個實體實現,然后使用該新接口作為通用函數參數約束。

如果您絕對確定屬性將具有相同的名稱,則可以傳遞一個dynamic來設置屬性值。 但是,這會阻止對類型進行任何編譯時檢查,因此如果您不小心傳遞了不兼容的類型,則直到運行時才會捕獲到它。

public void SetInitialProperties(dynamic dbEntity, DateTime createdTime, string creatorName) {
  dbEntity.CreatedTime = createdTime;
  dbEntity.CreatorName = creatorName;
}

你可以使用反射來實現你想要的。 您可以傳入一個對象並解析它的類型,然后獲取該給定類型的所有公共屬性,然后查找是否有一個名為CreatedTime 然后您可以在傳遞的dbEntity對象上設置給定屬性的值。 但是,我不推薦此解決方案:

public void SetInitialProperties(object dbEntity, DateTime createdTime, string creatorName) {
        // get the passed object's properties and find the one called CreatedTime
        var createdTimePropertyInfo = dbEntity.GetType().GetProperties().Where(i => i.Name == "CreatedTime").FirstOrDefault();
        // below line is equal to: dbEntity.CreatedTime = createdTime;
        createdTimePropertyInfo.SetValue(dbEntity, createdTime);
        
        var creatorNamePropertyInfo = dbEntity.GetType().GetProperties().Where(i => i.Name == "CreatorName").FirstOrDefault();
        creatorNamePropertyInfo.SetValue(dbEntity, creatorName);
    }

從長遠來看,您最好創建一個通用接口甚至抽象基類,這樣您就不必為每個 EF 模型實現 CreatedTime 和 CreatorName 以及其他屬性。 它看起來像下面這樣:

public interface IUserEntry
{
    DateTime CreatedTime { get; set; }
    string CreatorName { get; set; }
}

public abstract class UserEntryBase : IUserEntry
{
    public DateTime CreatedTime { get; set; }
    public string CreatorName { get; set; }
}

public partial class BlogPost : UserEntryBase
{
    public string PostTitle { get; set; }
    public string PostText { get; set; }
}

public partial class Comment : UserEntryBase
{
    public string CommentText { get; set; }
}

您的 SetInitialProperties 將非常簡單:

public void SetInitialProperties(IUserEntry dbEntity, DateTime createdTime, string creatorName)
    {
        dbEntity.CreatedTime = createdTime;
        dbEntity.CreatorName = creatorName;
    }

一旦開發到接口上,與使用反射或動態類型相比,您可以獲得更大的靈活性,因為您可以進行我之前提到的編譯時檢查,並且您可以看到模型的公共屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM