【问题标题】:Razor Pages - Passing abstract class to Html.Partial expecting an interface throws an errorRazor Pages - 将抽象类传递给期望接口的 Html.Partial 会引发错误
【发布时间】:2021-05-24 23:19:57
【问题描述】:

我有:

  1. 具有 2 个泛型的接口
  2. 实现接口和PageModel的抽象类
  3. 实现抽象类的剃须刀页面类
  4. 需要接口的部分

当我尝试@await Html.PartialAsync("_InterfacePartial", Model) 时,它会抛出以下内容

InvalidOperationException:传递到 ViewDataDictionary 的模型项的类型为“IndexPageModel”,但此 ViewDataDictionary 实例需要类型为“IPage`2[System.Object,IRow]”的模型项。

Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary.EnsureCompatible(对象值)

IPage.cs

public interface IPage<T, T2>
    where T : class
    where T2 : IRow
{
    // STRIPPED FOR BREVITY

    IQueryable<T> GetQueryable();

    IEnumerable<T2> GetResults(Func<T, bool> predicate);
}

AbstractPage.cs

public abstract class AbstractPage<T, T2> : PageModel, IPage<T, T2>
    where T : class
    where T2 : IRow
{
    // STRIPPED FOR BREVITY

    public abstract IQueryable<T> GetQueryable();

    public IEnumerable<T2> GetResults(Func<T, bool> predicate)
    {
        var fields = ... // Edit after answer => `var fields = GetTableFields();` returns Func<T, T2>

        return GetQueryable().Where(predicate).ToList().Select(fields);
    }
}

IndexPageModel.cshtml.cs

public class IndexPageModel : AbstractPage<Thing, ThingRow>
{
    public override IQueryable<Order> GetQueryable()
    {
        return MyDbContext.Things.Include(x => x.AnExtraThing);
    }
}

是我做错了什么,还是我只是一个试图做不可能的事的疯子?

【问题讨论】:

    标签: c# asp.net asp.net-core razor


    【解决方案1】:

    您的问题与一些概念有关:赋值兼容性协方差逆变

    协变保持赋值兼容性,而逆变反转赋值兼容性。你可以在这里了解更多信息Covariance & Contravariance

    因此,在这种情况下,您的页面声明的模型类型是一个带有通用参数的接口,但要支持从具有更多派生(更具体)类型的参数的某个实例接受(赋值)值,您需要支持 covariance 使用关键字out 为该接口类型,如下所示:

    public interface IPage<out T, out T2>
    where T : class
    where T2 : IRow
    { ... }
    

    幸运的是,这与接口方法中使用的返回类型相匹配。所以你可以看到TT2 都是返回类型。如果其中一个是参数类型,您将被卡住并应该考虑您的界面设计。下面是一个不能声明支持协方差的接口示例:

    //has compile-time error
    public interface IPage<out T, out T2>
    where T : class
    where T2 : IRow
    {    
       IQueryable<T> GetQueryable();
       IEnumerable<T2> GetResults(Func<T, bool> predicate);
       //just an example, this will violate the covariance rule
       //because T2 cannot be used as an input (argument) type once declared with out
       int ComputeSomeThing(T2 row);
    }
    

    所以在幕后,你的页面模型将被分配给IndexPageModel OK 像这样(仅用于演示目的,不完全是发生了什么):

    IPage<object,IRow> model = new IndexPageModel(...);
    

    更新

    关于一个更复杂的场景,其中泛型类型T 不直接用作参数类型或返回类型,而是用于另一个也具有变体泛型类型的泛型类型,例如Func。下面是一个这样复杂的例子:

    public interface IComplicatedVariant<in T, out TResult> {
         Func<T, TResult> GetFunc();
         void SetFunc(Func<TResult, T> f);
    }
    

    如您所见,返回类型Func&lt;T, TResult&gt; 接受具有相同方差的泛型参数类型T(逆变)和TResult(协变)(就像从类型IComplicatedVariant 向下级联)。但是,当使用Func 的参数类型时,我们需要反转Func 中传递的泛型类型的方差。因此,我们只能传递Func&lt;TResult,T&gt;,而不是Func&lt;T,TResult&gt;(无效并且会出现设计时错误),或者简而言之,T 不能在Func&lt;T, ...&gt; 中使用,TResult 不能在@987654343 中使用@。

    第二种方法SetFunc没有具体的例子就更难解释了。通用参数的差异似乎是颠倒了。 假设T 可以在Func&lt;T,...&gt; 中用于SetFunc,我们有:

    //T1, T2 here are concrete types, not generic argument types
    var a = new ComplicatedVariant<object,T2>();
    IComplicatedVariant<T1, T2> o = a;
    Func<T1,T2> f = ...;
    //OK
    o.SetFunc(f);
    //BUT this is not OK
    a.SetFunc(f);
    

    上面的例子是一个假设,表明在不扭转方差的情况下会出现什么问题(如上所述)。您可以看到a.SetFunc 需要Func&lt;object,T2&gt;,但传入的参数是Func&lt;T1,T2&gt;Func&lt;,&gt; 中的第一个参数类型是逆变(用in T 声明),因此它反转了赋值兼容性 em>,这意味着第一个通用参数类型左侧应该等于或更多派生在右侧输入。所以我们不能像这样分配函数(伪代码):

    //cannot be set like this, the reverse is however OK
    Func<object,T2> = Func<T1,T2>
    

    不能接受参数(在调用a.SetFunc 中),这意味着它违反了为接口IComplicatedVariant 设计的差异。所以我们最初的假设是无效的。正如我所说,我们使用一个具体的例子来展示一个无效的案例(假设)。这相当复杂,但幸运的是我们有设计时编译器,它可以帮助在设计时报告错误。如果您不确定,您可以在设计时尝试一下,看看它是否正常工作。

    【讨论】:

    • 谢谢,这真的很有帮助。我确实有几个具有Func&lt;T, ... 的方法,但我将它们移到了抽象类。
    • @glenn223 你是什么意思?这仍然不起作用吗?如果您有未发布的真实代码(这使得该解决方案不适用),请使用该代码更新您的问题,我可能会提供帮助。
    • 对不起,它确实有效。错误地删除了答案勾?‍♂️
    • 我的问题是接口中的一些方法返回Func&lt;T, T2&gt;(例如Func&lt;T, T2&gt; GetTableFields();)。这些显然是行不通的,所以我把它们移到了抽象类。但是,如果我误解了,您可以在界面中使用此功能,那么我会很乐意编辑我的问题
    • @glenn223 我认为返回Func&lt;...,T2&gt; 的方法有效,但Func&lt;T,T2&gt; 无效,因为ToutFunc`2 被声明为Func&lt;in T,out TResult&gt;。因此,当方法返回Func`2 时,不能将out T 放入in T。但是,可以使用 Func&lt;T,...&gt; 的参数(在您的代码中,我们有 Func&lt;T, bool&gt;)。请注意,当用于泛型类型(用于参数和返回类型)时,方差是相反的,方差类似于Func&lt;T,TResult&gt;。您可以轻松地自己尝试一下。我们没有解决此问题的方法,只需相应地调整您的设计即可。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多