【问题标题】:Generics covariance in constraint约束中的泛型协方差
【发布时间】:2016-10-07 10:42:57
【问题描述】:

我目前在 C# 中遇到泛型问题,我有一个解决方案,但我正在寻找一个更简洁的解决方案。我通常有很好的 C# 知识,所以我不确定我想要的是否存在。

我有一个带有嵌套约束的通用基类

public abstract class BaseParam { }

public abstract class BaseProcess<TParam> : IProcess
   where TParam : BaseParam
{
    public TParam Parameter { get; set; }
}

public abstract class BaseTask<TProc, TParam>
   where TProc : BaseProcess<TParam>, new()
   where TParam : BaseParam, new()
{
    public IProcess Create()
    {
        var proc = new TProc();
        proc.Param = new TParam();
        return proc;
    }
}

现在我可以创建几个派生,这工作了一段时间

public class SomeParam : BaseParam { }

public class SomeProcess : BaseProcess<SomeParam> { }

public class SomeTask : BaseTask<SomeProcess, SomeParam> { }

当我开始像这样混合继承级别时会出现问题

public class SuperParam : SomeParam { }

// The next line won't compile
public class SuperTask : BaseTask<SomeProcess, SuperParam> { }

我知道它不能编译,因为协方差只存在于接口上,Foo&lt;A&gt;Foo&lt;B&gt; 在技术上是两个不同的类。但是里面的代码可以正常工作。我想要的是写一个“协变约束”,如:

public abstract class BaseTask<TProc, TParam>
   where TProc : BaseProcess<TProcParam>
   where TProcParam : BaseParam
   where TParam : TProcParam
{
}

我尝试使用ISomeProc&lt;TParam&gt;,因为接口是协变的,但这仅适用于分配,而不适用于下限。功能变通方法是声明一个额外的类型参数,但这还不够:

public abstract class BaseTask<TProc, TProcParam, TParam>
   where TProc : BaseProcess<TProcParam>
   where TProcParam : BaseParam
   where TParam : TProcParam
{
}

编译器错误:

The type 'SomeProcess' cannot be used as type parameter 'TProc' in the
generic type or method 'BaseTask<TProc,TParam>'. There is no implicit
reference conversion from 'SomeProcess' to 'BaseProc<SuperParam>'.  

编辑:我明白需要更多解释。在类中我不需要SuperParam 的属性,只需要BaseParam,但它应该是SuperParam 的一个实例。然而,我们通过反射阅读了SuperTask 的定义并创建了一个编辑器。并且那个编辑器必须知道它应该创建什么对象。

问题:有没有办法设计BaseTask的约束,使其尊重协方差而不添加额外的参数TProcParam?我可以想出解决方法,但我觉得有更简单的方法。

【问题讨论】:

  • 您好,为了清楚起见,您也可以发布编译器错误吗?
  • “但是里面的代码可以正常工作”你怎么知道的?不看“里面的代码”很难知道。
  • 好的,您尝试了一些方法,但没有成功,但我想知道您的问题是什么?
  • 你的基类在TParam 中不是、不可能、永远不会是协变的,因为Parameter 有一个setter。碰巧 C# 不允许类变体(在我看来,这是一个很大的疏忽),但即使它允许你也不能使它成为协变的。
  • 如果你希望你的类型参数是协变的。您可以在通用接口和委托中使用 out 关键字。尝试使用@ArturoMenchaca 的方法

标签: c# generics


【解决方案1】:

您的问题的解决方案在很大程度上取决于您的类是如何实现的,但如果您可以将BaseProcess&lt;&gt; 转换为协变接口IBaseProcess&lt;&gt;

public interface IBaseProcess<out TParam>
   where TParam : BaseParam
{
}

并将BaseTask 声明为:

public abstract class BaseTask<TProc, TParam>
    where TProc : IBaseProcess<BaseParam>
    where TParam : BaseParam
{
}

然后,SomeProcess 为:

public class SomeProcess : IBaseProcess<SomeParam> { }

您可以随意声明SuperTask

public class SuperTask : BaseTask<SomeProcess, SuperParam> { }

【讨论】:

  • 我可以,但是编译器无法告诉我SomeProcessSuperProcess 是兼容的。考虑一下,我想做的事情可能对编译器没有意义 - 我希望他检查它是否兼容,而不是严格......
  • @Toxantron:我认为没有中间类型就没有办法这样做,因为一方面你想要一个比BaseParam 更专业的处理器(第一个泛型参数)(只能是协方差或添加第二个泛型参数),另一方面是对处理器有效的参数,但不一定是相同类型(第三个泛型参数)。
【解决方案2】:

根据 cmets 和进一步检查,我注意到两件事:

  1. 示例不完整,分散了手头的问题。
  2. 我想做的事情编译器无法理解

我现在将采用的解决方案是将变通办法与保持默认行为清洁的努力相结合:

// Use workaround on BaseTask
public abstract class BaseTask<TProc, TProcParam, TParam>
   where TProc : BaseProcess<TProcParam>
   where TProcParam : BaseParam
   where TParam : TProcParam
{
}

// Generic shortcut overload
public abstract class BaseTask<TProc, TParam> : BaseTask<TProc, TParam, TParam>
   where TProc : BaseProcess<TProcParam>
   where TParam : TProcParam
{
}

这样我可以同时做到:

public class SomeTask : BaseTask<SomeProcess, SomeParam> { }

public class SuperTask : BaseTask<SomeProcess, SomeParam, SuperParam> { }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-29
    • 2020-11-20
    • 2011-02-09
    • 1970-01-01
    • 1970-01-01
    • 2018-04-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多