C# - 我可以从具有泛型基类型的类派生吗?

PJD*_*Dev 2 c#

介绍

我正在使用 Enity Framework 创建一个 ASP.NET WebAPI 应用程序。我需要做的是根据用户角色为一个 URI 返回相同资源的不同表示。例如,api/employees/1将为管理员和标准用户返回两个不同的对象:

标准用户

public class EmployeeBasic {
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

行政

public class EmployeeExtended : EmployeeBasic {
    public decimal Salary { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

想法和尝试

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

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

public interface ISortModel<out TBusinessEntity> {

}
Run Code Online (Sandbox Code Playgroud)

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(IEnumberable<TBusinessEntity> localList);

    // ...
    // Some private logic (expression mappers, etc.)
}
Run Code Online (Sandbox Code Playgroud)

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(IEnumberable<EmployeeBasic> localList) {
        // implementation
    }
}
Run Code Online (Sandbox Code Playgroud)

4) 扩展基本排序模型,为扩展资源属性添加排序

public class EmployeeExtendedSortModel : EmployeeBasicSortModel //, ... Is it possible to somehow do that?
{
   public override IEnumerable<EmployeeExtended> ApplyToLocal(IEnumberable<EmployeeExtended> localList) {
        var partiallyOrderedList = base.ApplyToLocal(localList);

        // Add extended sorting
   }

   // ... ?
}
Run Code Online (Sandbox Code Playgroud)

5)使用上述类创建通用服务:

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

}
Run Code Online (Sandbox Code Playgroud)

问题

当我第一次想到它时,它似乎很简单。但是当我开始实现它时,我无法弄清楚实现第 4 步的方法。要么我在 C# 知识中遗漏了一些东西(这很有可能),要么我试图这样做是不可能的。所以问题是:我可以创建一个具有泛型类型的基类,用基本资源作为一种类型从它派生,然后再用扩展类派生一次吗?

Eri*_*ert 5

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

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) { ... }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

这个特性被称为返回类型协方差,它已经被建议用于 C#,哦,大约 15 年了,但从未实现。它不受 CLR 支持,也不是设计团队的高优先级,它创造了脆性基类问题的新口味,所有这些都是反对的,它不太可能很快达到标准.

C++确实支持这个特性,包括在CLR上,所以在CLR上是可以做到的。你最终只需要生成一堆辅助方法。

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

参数类型呢?这可能是类型安全有D.Frob初具规模。同样,同样的推理:如果我们将 D 转换为 B,那么我们只会得到正方形。但是让 D.Frob 拿走 GreenSquare 是不安全的,因为 B.Frob 承诺能够拿走任何方块,而不仅仅是绿色方块。

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

现在,您需要返回类型 covariance 和形式参数类型covariance,这既不受支持也不类型安全。有趣的是,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);
  }
}
Run Code Online (Sandbox Code Playgroud)

无论如何,这基本上是 C# 编译器必须代表您执行的操作。