【问题标题】:Error trying to cast to generic type: redundant cast尝试转换为泛型类型时出错:冗余转换
【发布时间】:2016-05-11 13:04:55
【问题描述】:

我有一个通用函数,它返回AnyListVM 的子类的新实例,基本上是这样实现的:

    public TListVM MakeListVM<TListVM>()
        where TListVM : AnyListVM
    {
        TListVM listVM;
        switch(typeof(TListVM).ToString())
        {
            case nameof(EventListVM):
                listVM = new EventListVM();
                // some more init stuff
                break;

            // some more similar cases

            default:
                throw new NotImplementedException();
        }
        return listVM;
    }

这两个涉及的类目前看起来像这样,还没有任何有意义的实现:

public abstract class AnyListVM
{
}

public class EventListVM : AnyListVM
{
}

现在 Visual Studio 强调了我的 new EventListVM() 并唠叨它不能将 EventListVM 隐式转换为 TListVM

好的,所以我想我只是添加一个显式演员:

        listVM = (TListVM)new EventListVM();

但是没有。现在 Visual Studio 再次强调它并说它是一个多余的演员表。提供的自动修复将是再次删除演员表。无限循环。

这里出了什么问题,为什么不允许我显式或隐式地执行此转换?

【问题讨论】:

  • 奇怪的是,如果我写listVM = (TListVM)Activator.CreateInstance(typeof(TListVM)); 而不是listVM = new EventListVM();,它就不符合要求。为什么会这样?
  • 语句listVM = (TListVM)Activator.CreateInstance(typeof(TListVM)); 没有对具体类型的引用,因此在这种情况下编译器不必尝试转换为EventListVM 或从EventListVM 转换。

标签: c# generics casting


【解决方案1】:

您的实现中有一个明显的问题是不正确的,其他人已经指出但没有令人满意地解决。如果您打算实例化 TListVM,那么您需要更改两个非常重要的部分。首先是新的代码清单:

public TListVM MakeListVM<TListVM>()
    where TListVM : AnyListVM, new()
{
    TListVM listVM = new TListVM();

    EventListVM evtList = listVM as EventListVM;
    if (evtList != null)
    {
        // set evtList properties.  You can't change
        // the instantiation method.
    }

    // repeat for other constructs.

    return listVM;
}

现在,稍微解释一下。通用 where 子句需要指定您打算使用无参数构造函数创建 TListVM。为此,您需要将new() 指定为通用约束。

这大大简化了您的实现,它只知道有一个名为TListVM 的东西具有AnyListVM 的基类并且有一个没有参数的构造函数。不需要复杂的 switch 语句,也不需要使用Activator

在处理泛型时,请直接使用泛型参数。


根据进一步的信息,switch 语句仍然是错误的工具。泛型必然会限制您使用对象的方式。您无法更改构造函数,但您可以在对象实例化后专门设置属性。

上面我更改了列表以显示如何直接设置这些属性。

如果相反,您对拥有不同的构造函数等死心塌地,那么您将不得不以不同的方式处理它。您必须返回基类并且 TListVM

public AnyListVM MakeListVM<TListVM>()
    where TListVM : AnyListVM
{
    return MakeListVM(typeof(TListVM)) as TListVM;
}

private AnyListVM MakeListVM(Type listVM)
{
    AnyListVM listVM;
    switch(listVM.ToString())
    {
        case nameof(EventListVM):
            listVM = new EventListVM();
            // some more init stuff
            break;

        // some more similar cases

        default:
            throw new NotImplementedException();
    }
    return listVM;
}

通用帮助器方法允许您包装更通用的工厂方法,使其具有您想要的签名,而不会导致编译错误。

【讨论】:

  • 谢谢,但是有一些数据要作为特定于类型的初始化传递,所以无论如何我都需要开关。
  • 如果你实现了一个开关,那么你需要将不同的配置设置为属性。你不能作为构造函数来做。如果是这样的话,泛型是错误的工具。
  • @ByteCommander,添加了两个替代方案来获得您想要的东西,解决泛型的限制并解决它们。
  • 谢谢,你的上层代码 sn-p 看起来很有趣。我不会想到那种模式。
  • 我不知道您可以在指定约束的泛型类型上调用 new!好的。 +1!
【解决方案2】:

您不能保证EventListVM 将转换为TListVM,因为根据您的通用限制,允许传递AnyListVM 的任何继承类,它可能是也可能不是EventListVM。例如,如果此方法的调用者这样做:

AnyListVM vm = MakeListVM<SomeOtherListVMConcrete>();

它会失败,但不应该。

我相信您真正想要的是将您的 EventListVM 转换为 AnyListVM,即实际的基本类型而不是泛型。

AnyListVM listVM = new EventListVM();

不过,如果您总是返回EventListVM 的实例,我会考虑一起删除通用子句并更新签名以具有EventListVM 的返回类型。

【讨论】:

  • 对,但实际上我根据泛型参数决定了我选择创建实例的类型,因此创建的对象将始终匹配泛型类型TListVM。忘记说了。
  • 那么您MakeListVM 的实际代码与帖子中的不同吗?
  • 我刚刚编辑了它。我写的几行都是一样的,但周围还有几行。
  • 我明白了。谢谢,那更清楚了。然而,答案仍然有效。第一行应该将listVM 变量声明为AnyListVM 类型,您的编译器错误就会消失。我们可以看到 TListVM 始终是您分配的正确类型,因为 switch 语句,但编译器无法推断出它,因为它是运行时解析。
  • 但是我在返回行中收到错误,说我无法将 AnyListVM 隐式转换为 TListVM。那我必须在那里添加显式转换吗?
【解决方案3】:

为了使您的问题更加明显,请考虑一个基类和两个子类:

public class Base { }           //AnyListVM
public class Child1 : Base{ }   //EventListVM
public class Child2 : Base{ }   //OtherListVM

现在你的方法看起来像:

public T Get<T>() where T : Base
{
    //code
    T item = new Child1();
    //more code
}

想象一下我发送Child2,从方法签名来看这是完全有效的。该方法的代码现在如下所示:

Child2 item = new Child1();

这在编译时当然是无效的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    相关资源
    最近更新 更多