【问题标题】:Is there a way to type check a generic function parameter using the shape of the object instead of inheritance?有没有办法使用对象的形状而不是继承来检查泛型函数参数?
【发布时间】:2021-10-18 21:23:53
【问题描述】:

我有几个想要创建的 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 的"duck typing")。我想要的方法如下所示:

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;但是,如果可能的话,我想继续进行类型检查。

【问题讨论】:

  • 按照您的编码方式,您正在根据何时调用 new 来计算“创建时间”。我建议应该在实际创建记录时调用它,这可能会稍晚一些。您可以使用.HasDefaultValueSql("getutcdate()")SqlDefaultValueAttribute 来实现。
  • 你为什么不能为此创建一个额外的接口并让他们实现呢?您可以使用 partial 类,这样 EF 就不会覆盖它

标签: c# .net generics


【解决方案1】:

您可以使用反射实现您想要的。您可以传入一个对象并解析它的类型,然后获取该给定类型的所有公共属性,并查找您是否有一个名为 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;
    }

开发到接口后,您将获得比使用反射或动态类型更大的灵活性,因为您获得了我之前提到的编译时检查,并且您可以看到模型的共同属性。

【讨论】:

  • 感谢您的评论!我在 Gwinn 的解决方案上留下了类似的回复,但简而言之,我正在使用的类是自动生成的。您是否知道是否有某种方法可以防止接口被覆盖或告诉 EF 为它们分配接口?
  • @BenjaminU。 partial .edmx 中单独文件夹中的类不会被覆盖
【解决方案2】:

你不能在 C# 中这样做,因为 C# 使用 nominal type system 而不是 structural type system
对于您的特定情况,您必须提出一个包含共同属性并且将由两个实体实现的接口,然后使用该新接口作为通用函数参数约束。

【讨论】:

  • 感谢您的评论!我想使用接口;但是,EF 模型是自动生成的,因此每次我更新 edmx 时,我所做的手动更改都会被覆盖。你知道有没有办法解决这个问题?
【解决方案3】:

如果您完全确定属性将具有相同的名称,您可以传递 dynamic 来设置属性值。但是,这会阻止对类型的任何编译时检查,因此如果您不小心传递了不兼容的类型,它直到运行时才会被捕获。

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

【讨论】:

    猜你喜欢
    • 2018-07-05
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 2021-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多