【问题标题】:In C# can I cast MyService<Child> to MyService<Base>?在 C# 中,我可以将 MyService<Child> 转换为 MyService<Base> 吗?
【发布时间】:2017-05-30 08:54:48
【问题描述】:

背景:我们有一个基于 ASP.NET Core 的 Web 应用程序,它使用基于 DI 和通用存储库模式开发的 EF Core。因此,大多数事情都是使用接口完成的。现在,我们已经到了主表维护模块。我们不想为所有 10-20 个主表复制相同的服务(由存储库支持)类。

因此,我们创建了一个_ModelMasterBase 类并从中派生了所有主表类。所有主表的 CRUD 都是相同的。所以,接下来我们实现了MasterRepository&lt;T&gt;MasterService&lt;T&gt; 和它们的接口。现在一切都必须使用&lt;T&gt; 其中T 是页面上选择的主表的类型来执行CRUD。

最初,我希望IMasterService&lt;_ModelMasterBase&gt; 的实例可以转换为 IMasterService&lt;T&gt; - 再次,T 可以是任何子类 派生_ModelMasterBase - 但这似乎是不可能的! 我已经尝试过运算符、强制转换以及几乎所有我可以用谷歌搜索的东西!同样由于存储库模式,所有内容都必须是强类型的。

现在,我们已经按照SO Post 使用技巧将子 obj 转换为基类 obj -

DerivedClass B = new DerivedClass();
BaseClass bc = JsonConvert.DeserializeObject<BaseClass>(JsonConvert.SerializeObject(B));

我知道它有点肮脏,但有时它可以方便地在设计和复杂性之间进行权衡。我们谨慎使用它。我希望有类似的东西,以防我想将 MyService&lt;Base&gt; 转换为 MyService&lt;Child&gt;

或者您可以忘记这一切并指导我为我的所有主表提供单点 CRUD 服务 - 将相同的东西复制 10-20 次似乎是不合理的。抱歉,我无法深入解释,因为它会拉长帖子。

这是一个 v.basic sample of my code structure,最后您会看到我们正在努力实现的目标。希望对您有所帮助。


解决方案:

根据 mkArtak 的建议,我能够通过使用来破解它 'Covariance' 概念(example)。这是我的updated code sample. 现在所有主表都有一个控制器和服务层!

【问题讨论】:

  • 这不起作用的常见例子是List&lt;string&gt;。如果您可以将其转换为 List&lt;object&gt;,那么任何人都可以将 anything 添加到列表中,这肯定会混淆任何期望 List&lt;string&gt; 只能包含 strings 的代码。跨度>
  • 同意,但就我而言,我们想知道是否有办法以某种方式实现它 - 知道确切的装箱和拆箱场景。就像我们可以做的那样 - List<_modelbasemaster> result = data.Cast<_modelbasemaster>().ToList(); - 其中 data 可以是 List 并且 T 派生自 _ModelBaseMaster。

标签: c# casting asp.net-core polymorphism generic-programming


【解决方案1】:

简答:在 IMasterService 定义前缀 T 中带有 'out' 关键字。

public interface IMasterService<out T>
{
    // your existing methods' definitions here
}

称为协方差,在MSDN 中有描述。

【讨论】:

  • 谢谢。您能否抽出一些时间来看看我的简短代码示例,并帮助我了解您的解决方案将如何解决它。
  • @HemantTank 问题是你只能使用协变类型作为返回类型,不能像代码示例所建议的那样用于输入参数
  • @HemantTank,修复实际上是在 IMasterService 定义中为类型参数添加“out”。它将强制 IMasterService 和 IMasterService 实现之间的兼容性,因此您将能够编写如下内容:IMasterService baseMasterService = childMasterService; // 如果 childMasterService 是 IMasterService 类型。在 VB.Net 中,您应该使用以下接口声明: Interface IMasterService End Interface
  • 你是生命的救星!我非常努力,以至于这么小的修复似乎是不可能的。我的下一步是解决我的 CRUD 例程,例如 - void Add(T mObj);错误:无效方差:类型参数“T”必须在“IMasterService.Add(T)”上逆变有效。 'T' 是协变的
  • 嘿@HemantTank,看起来你必须使 Add 方法通用如下: void Add(V v) where V : BaseClass;
【解决方案2】:

继承问题?

这种事情听起来你应该更喜欢组合而不是继承。正如您所说,您能够将您的专用类序列化/反序列化为您的基类,这不是更像是定义上的数据传输对象吗?我不会将逻辑放入其中,而是引入另一个使用基类逻辑而不继承的服务(例如,将实例传递给构造函数)——这可能首先解决问题。

不是解决办法

对于您的特定问题:如果这不是太严格(例如,您只能在参数中使用类型参数而不是在返回类型中)并且您拥有相关接口,则您可能可以使用 contravariant type parameter。但是编译器只允许我以相反的方式分配服务——这是有道理的。也许您会以 协变 的方式找到解决方案?

using System;

public class Program
{
    internal interface IMasterService<in T> {
        void DoSomething(T table);
    }

    internal class BaseClass {}

    internal class DerivedClass : BaseClass {}

    internal class SpezializedService : IMasterService<BaseClass> {
        public void DoSomething(BaseClass table) {}
    }

    public static void Main()
    {
        // The compiler isn't happy about the other way around
        IMasterService<DerivedClass> baseService = new SpezializedService();
    }
}

希望这会有所帮助。

【讨论】:

  • 感谢您的详细解释,我想它已经接近了。您能否抽出一些时间来看看我的简短代码示例,并帮助我了解您的解决方案将如何解决它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 2012-03-22
  • 2011-12-21
  • 1970-01-01
相关资源
最近更新 更多