【问题标题】:C# - can I derive from class with generic base type?C# - 我可以从具有通用基类型的类派生吗?
【发布时间】:2022-02-01 00:54:21
【问题描述】:

简介

我正在使用实体框架创建一个 ASP.NET Web API 应用程序。我需要做的是为一个 URI 返回同一资源的不同表示,具体取决于用户角色。例如,api/employees/1 将为管理员和标准用户返回两个不同的对象:

标准用户

public class EmployeeBasic 
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

管理员

public class EmployeeExtended : EmployeeBasic 
{
    public decimal Salary { get; set; }
}

想法和尝试

对于每个资源表示,我需要提供一些相关的类,例如排序模型。我想知道是否可以使用泛型类型和继承来为相关表示创建通用存储库方法。我想到了以下方法:

  1. 为排序模型创建一些基本接口:

     public interface ISortModel<out TBusinessEntity> 
     {
         //
     }
    
  2. 创建通用 SortModel 作为所有排序模型的基本类型

      public abstract class SortModel<TDBEntity, TBusinessEntity> : ISortModel<TBusinessEntity>
      {
           // Database sorting
           public abstract IQueryable<TDBEntity> ApplyToQuery(IQueryable<TDBEntity> query);
    
           // Local sorting
           public abstract IEnumerable<TBusinessEntity> ApplyToLocal(IEnumerable<TBusinessEntity> localList);
    
           // ...
           // Some private logic (expression mappers, etc.)
     }
    
  3. 为基本资源创建排序模型

     public class EmployeeBasicSortModel : SortModel<DBModel.Employee, EmployeeBasic>
     {
         public int FullName { get; set; }
    
         public override IQueryable<DBModel.Employee> ApplyToQuery(IQueryable<DBModel.Employee> query) 
         {
             // implementation
         }
    
         public override IEnumerable<EmployeeBasic> ApplyToLocal(IEnumerable<EmployeeBasic> localList) 
         {
             // implementation
         }
     }
    
  4. 扩展基本排序模型,为扩展资源属性添加排序

     public class EmployeeExtendedSortModel : EmployeeBasicSortModel //, ... Is it possible to somehow do that?
     {
         public override IEnumerable<EmployeeExtended> ApplyToLocal(IEnumerable<EmployeeExtended> localList) 
         {
             var partiallyOrderedList = base.ApplyToLocal(localList);
    
             // Add extended sorting
         }
    
         // ... ?
     }
    
  5. 使用上述类创建通用服务:

     class EmployeesService() 
     {
         public IList<TEmployee> GetAll<TEmployee>(ISortModel<TEmployee> sortModel)
                where TEmployee : BasicEmployee
         {
             // implementation
         }
     }
    

问题

当我第一次想到它时,它似乎很简单。但是当我开始实现这个时,我无法弄清楚实现第 4 步的方法。要么我在我的 C# 知识中遗漏了一些东西(这很可能),要么这在我试图做到这一点的方式中是不可能的。

所以问题是:我可以创建一个具有泛型类型的基类,从它派生出基本资源作为一种类型,然后再派生一次扩展类吗?

【问题讨论】:

  • 听起来过于复杂了。你想达到什么目的?
  • 如果我理解正确,您是在问是否可以继承类型参数是一回事的泛型类型(例如EmployeeBasic),然后使用不同类型覆盖成员(例如EmployeeExtended) .如果是这样,那么答案是否定的,你不能这样做,出于同样的原因,对于问同样事情的数百个其他相同的 Stack Overflow 问题,答案是“否”:它不是类型安全的。如果您要问其他问题,请编辑您的问题,以便清楚您在问什么。请务必解释为什么这是特定于 ASP.NET,因为您添加了这些标签。
  • 谢谢彼得,我认为这回答了我的问题。 @Mardoxx 也许是这样,但我也给出了过于简单的例子。我有更多的类,每个类都有比示例中更多的属性。一些属性在数据库中加密,这就是为什么我需要单独的数据库和本地排序/过滤。拥有自我解决的排序/过滤模型让我可以准备一个通用查询机制,而不是每个服务方法中的一堆 if/else 语句:pastebin.com/Hsp095L8 - 这在内部使用过滤器执行查询,解密实体并应用本地排序/过滤
  • 然而,在我意识到我需要为每个资源提供多个表示之前,它要方便得多。然后,我想要实现的是:(1)避免从数据库中获取扩展实体,然后将其映射到受限视图模型(这将执行不需要的 sql 查询),因此:(2)修改我的通用查询机制以支持不同的资源表示.但是,它确实似乎太复杂了,我将创建单独的服务方法或使用(1)方法。
  • 不要使用继承来实现这一点,并且基于您试图操纵 行为 而不是数据的事实,为什么不使用 Func 作为参数你的排序逻辑。然后您的类可以以不同的方式传递给基排序逻辑。您在基类中定义基本排序框架,然后派生类传递它们希望使用的排序行为。

标签: c#


【解决方案1】:

天哪,这是一个复杂的问题。仿制药是一个巨大的红鲱鱼。忽略泛型;问题更为根本。让我们大大简化它。

class Animal {}
class Mammal : Animal {}
class Tiger : Mammal {}

class Shape {}
class Square : Shape {}
class GreenSquare : Square {}

class B
{
  public virtual Mammal Frob(Square s) { ... }
}

class D : B
{
  public override SomeReturnType Frob(SomeArgumentType m) { ... }
}

问题是:这个虚拟覆盖的合法返回和参数类型是什么?

答案是:在 C# 中,唯一合法的类型是那些与被覆盖方法的类型完全匹配的类型。 Frob 的覆盖必须返回 Mammal 并占据 Square。

现在,我们可以在理论上使 D.Frob 的类型安全返回 Tiger。你明白为什么吗?如果我们将 D 转换为 B,那么它会返回 Tiger,但 Tiger 是 Animal,所以我们没问题。

这个特性叫做return type covariance,它被建议用于C#已经有15年了,但从未实现过。它不受 CLR 支持,也不是设计团队的高优先级,它创造了脆性基类问题的新口味,所有这些都是反对它不太可能很快达到标准的点.

C++ 确实支持此功能,包括在 CLR 上,因此在 CLR 上是可能。你最终只需要生成一堆辅助方法。

当然,我们不能让 D.Frob 返回 Animal。它可以返回乌龟,但 B.Frob 承诺只返回哺乳动物。

参数类型呢?让 D.Frob 成形可能是类型安全的。同样的道理:如果我们将 D 转换为 B,那么我们只会得到正方形。但是让 D.Frob 选择 GreenSquare 并不安全,因为 B.Frob 承诺能够选择任何方格,而不仅仅是绿色方格。

这个特性被称为形式参数类型逆变,很少有语言实现它。

现在,您需要返回类型协方差和形参类型协方差,这既不支持也不类型安全。有趣的是,Eiffel 支持这种协方差。

想要返回类型协方差的 C# 开发人员通常最终会执行以下操作:

class D : B {
  private Tiger FrobPrivate(Square s) { ... }
  public override Mammal Frob(Square s) 
  { 
    return this.FrobPrivate(s);
  }
  public new Tiger Frob(Square s)
  { 
    return this.FrobPrivate(s);
  }
}

这基本上是 C# 编译器必须代表您执行的操作才能实现该功能。

【讨论】:

  • 感谢您提供如此详尽的回答!这是一堂非常有趣的编程课。看来我需要再次重新考虑整个想法。返回类型协方差的解决方法很有趣,我非常感谢你给出了这个例子。也许我什至可以在我的代码中以某种方式使用它,但是,我会尝试找到一种方法来真正避免使用它,因为我开始注意到我的思维方式中的一些错误。
猜你喜欢
  • 2014-04-30
  • 1970-01-01
  • 2021-03-08
  • 1970-01-01
  • 2012-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多